تغییر یک متغیر وضعیت (State) رندر جدیدی را در صف قرار میدهد. اما گاهی اوقات ممکن است بخواهید پیش از اعمال رندر بعدی، چندین عملیات مختلف را روی مقدار آن استیت انجام دهید. برای این کار، لازم است درک کنید که React چگونه آپدیتهای استیت را دستهبندی (Batch) میکند.
مفهوم Batching چیست و چطور پردازش همزمان استیتها سرعت اپلیکیشن را بالا میبرد؟
چطور چند آپدیت پیاپی را روی یک استیت واحد اعمال کنیم؟
بررسی منطق ریاضی صفِ استیتها در رندر بعدی.
در درس قبل دیدیم که چرا صدا زدن سه بارِ setNumber(number + 1) مقدار شمارنده را به جای ۳ واحد، فقط ۱ واحد افزایش میدهد. دلیل اول ثابت بودن مقدار متغیر در آن اسنپشات بود، اما دلیل دوم به نحوه پردازش این دستورات توسط React برمیگردد.
React منتظر میماند تا تمام کدهای درون یک Event Handler کاملاً اجرا شوند و سپس به سراغ پردازش استیتها و رندر صفحه میرود.
این رفتار دقیقاً شبیه به یک گارسون در رستوران است. وقتی شما اولین غذای خود را نام میبرید، گارسون فوراً به سمت آشپزخانه نمیدود! او صبورانه منتظر میماند تا سفارش شما و سایر افرادِ دور میز تمام شود، تغییرات نهایی را یادداشت میکند و سپس یکبار کل سفارش را به آشپزخانه میبرد.
این مکانیزم (Batching) به شما اجازه میدهد چندین متغیر وضعیت (حتی از کامپوننتهای مختلف) را بدون ایجاد رندرهای مکرر و بیهوده بهروزرسانی کنید. این کار مانع از رندرهای نصفهونیمه (جایی که فقط برخی از متغیرها آپدیت شدهاند) میشود و سرعت اجرای برنامهتان را به شدت افزایش میدهد.
⚡ نکته: React رویدادهای عمدی و مجزا (مانند دو کلیک جداگانه کاربر) را با هم دستهبندی نمیکند؛ هر کلیک کاملاً مستقل پردازش میشود. Batching فقط در مواردی اعمال میشود که کاملاً امن است؛ مثلاً اگر کلیک اول فرم را غیرفعال (
disabled) کند، دستهبندی مانع از این میشود که کلیک دوم فرم را دوباره ارسال کند.
اگر نیاز دارید یک استیت واحد را چندین بار پشت سر هم و در یک رویداد تغییر دهید، به جای پاس دادن یک مقدار مستقیم (مثل setNumber(number + 1))، باید یک تابع به آن پاس دهید که مقدار بعدی را بر اساس وضعیت قبلی موجود در صف محاسبه کند: setNumber(n => n + 1).
این تابع اصطلاحاً Updater Function نام دارد. با این روش شما به React میگویید: «یک عملیات روی این مقدار انجام بده»، به جای اینکه بگویید «این مقدار را کلاً جایگزین کن».
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
// پاس دادن تابع آپدیتکننده به جای مقدار خام
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>۳+</button>
</>
);
}
حالا با کلیک روی این دکمه، عدد روی صفحه ۳ واحد زیاد میشود. بیایید ببینیم در پشت صحنه چه رخ میدهد:
تابع اول n => n + 1 یک تابع است. React آن را به صف (Queue) اضافه میکند.
تابع دوم n => n + 1 به صف اضافه میشود.
تابع سوم n => n + 1 نیز به صف اضافه میشود.
وقتی در رندر بعدی React میخواهد مقدار useState را زنده کند، به سراغ این صف میرود. مقدار قبلی استیت 0 بود، پس React عدد 0 را به عنوان آرگومان n به اولین تابع میدهد:
| تابع موجود در صف | مقدار ورودی (n) | مقدار خروجی (Return) |
n => n + 1 | 0 | $0 + 1 = \mathbf{1}$ |
n => n + 1 | 1 | $1 + 1 = \mathbf{2}$ |
n => n + 1 | 2 | $2 + 1 = \mathbf{3}$ |
در نهایت React عدد ۳ را به عنوان نتیجه نهایی ذخیره کرده و از useState برمیگرداند.
اگر یک مقدار خام را با یک تابع آپدیتکننده ترکیب کنیم چه اتفاقی میافتد؟ به این مثال توجه کنید:
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}>
در رندر بعدی عدد چه خواهد بود؟ (فرض کنید مقدار اولیه 0 است).
۱. دستور setNumber(number + 5): از آنجا که number صفر است، دستور تبدیل میشود به setNumber(0 + 5). ریآکت عبارت «جایگزین کن با ۵» را به صف اضافه میکند.
۲. دستور setNumber(n => n + 1): یک تابع آپدیتکننده است، پس به صف اضافه میشود.
در رندر بعدی، صف به این صورت حل میشود:
آیتم اول صف: «جایگزین کن با ۵» $\rightarrow$ مقدار ورودی صفر نادیده گرفته شده و خروجی برابر ۵ میشود.
آیتم دوم صف: تابع n => n + 1 $\rightarrow$ مقدار قبلی ۵ است، پس خروجی میشود: $5 + 1 = \mathbf{6}$.
نتیجه نهایی ذخیره شده برابر ۶ خواهد بود.
اگر ترتیب را برعکس کنیم چه؟ به کد زیر نگاه کنید:
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42); // جایگزینی نهایی
}}>
بیایید صف این رویداد را تشکیل دهیم:
دستور اول: «جایگزین کن با ۵» (چون number صفر بود).
دستور دوم: تابع n => n + 1.
دستور سوم: «جایگزین کن با ۴۲».
در زمان رندر مجدد، خطوط صف یکییکی اجرا میشوند:
آیتم اول: خروجی میشود ۵.
آیتم دوم: عدد ۵ وارد تابع شده و خروجی میشود ۶.
آیتم سوم: «جایگزین کن با ۴۲» $\rightarrow$ مقدار قبلی (۶) کاملاً نادیده گرفته شده و خروجی نهایی برابر ۴۲ میشود!
📝 فرمول کلی پردازش صف:
یک تابع آپدیتکننده (مثل
n => n + 1) به صف اضافه شده و روی دیتای قبلی محاسبه انجام میدهد.هر مقدار دیگر (مثل عدد یا رشته)، دستور «جایگزین کن با این مقدار» را به صف اضافه کرده و هر آنچه از قبل در صف محاسبه شده بود را برای خط خودش نادیده میگیرد.
توابع آپدیتکننده در طول فاز رندر اجرا میشوند؛ بنابراین این توابع باید کاملاً خالص (Pure) باشند و فقط نتیجه محاسبه را برگردانند. هرگز تلاش نکنید از داخل این توابع، استیت دیگری را تغییر دهید یا عوارض جانبی (مثل درخواست شبکه یا لاگ) ایجاد کنید. در حالت Strict Mode، ریآکت هر کدام از این توابع را دو بار اجرا میکند تا از خالص بودن آنها مطمئن شود (اما نتیجه اجرای دوم را دور میریزد).
در دنیای برنامهنویسی React، مرسوم است که نام آرگومان ورودی تابع آپدیتکننده را بر اساس حروف اول متغیر استیت انتخاب کنند:
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
اگر کدهای خواناتر و طولانیتر را ترجیح میدهید، استفاده از خود نام استیت یا اضافه کردن پیشوند prev (مخفف Previous) نیز بسیار رایج و استاندارد است:
setEnabled(enabled => !enabled);
setEnabled(prevEnabled => !prevEnabled);
تغییر دادن استیت، متغیر موجود در رندر فعلی را عوض نمیکند، بلکه درخواست یک رندر جدید را ثبت میکند.
React فرآیند آپدیت استیتها را پس از پایان کامل توابع رویداد انجام میدهد که به آن Batching میگویند.
برای اعمال چند آپدیت متوالی روی یک استیت در یک رویداد واحد، همیشه از تابع آپدیتکننده setNumber(n => n + 1) استفاده کنید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn