در دنیای برنامهنویسی، برخی از توابع «پاک» (Pure Functions) هستند. توابع پاک فقط و فقط یک کار انجام میدهند: محاسبه و دیگر هیچ! اگر کامپوننتهای React خود را آگاهانه و با لجاجت به صورت توابع پاک بنویسید، با بزرگ شدن پروژه جلوی دسته عظیمی از باگهای مرموز، عجیب و غیرقابلپیشبینی را میگیرید.
مفهوم «پاک بودن» (Purity) چیست و چطور به ما در حذف باگها کمک میکند؟
چطور با دور نگه داشتن تغییرات از فاز رندر، کامپوننتها را پاک نگه داریم؟
ابزار Strict Mode چطور اشتباهات پنهان ما را مچگیری میکند؟
در علوم کامپیوتر (بهویژه برنامهنویسی تابعی)، یک تابع زمانی پاک (Pure) نامیده میشود که دو ویژگی طلایی داشته باشد:
سرش به کار خودش باشد: به هیچ شیء یا متغیری که قبل از فراخوانی تابع وجود داشته، دستدرازی نکند و آن را تغییر ندهد.
ورودی یکسان، خروجی یکسان: اگر ۱۰۰ بار هم یک ورودی ثابت به آن بدهید، هر ۱۰۰ بار دقیقاً یک خروجی یکسان پس بگیرید.
بهترین مثال برای درک این موضوع، فرمولهای ریاضی هستند. فرمول را در نظر بگیرید:
اگر باشد، همیشه است.
اگر باشد، همیشه است. مقدار y هیچوقت به خاطر ساعت روز، آبوهوا یا وضعیت بازار بورس تغییر نمیکند!
شکل جاوااسکریپتی این فرمول به این صورت است:
function double(number) {
return 2 * number;
}
تابع double یک تابع کاملاً پاک است. ریاکت دقیقاً بر پایه این فلسفه بنا شده است و فرض را بر این میگذارد که تمام کامپوننتهای شما توابعی پاک هستند. یعنی یک کامپوننت با ورودیهای مشخص (Props)، باید همیشه ظاهر (JSX) یکسانی تولید کند:
function Recipe({ drinkers }) {
return (
<ol>
<li>جوشاندن {drinkers} پیمانه آب.</li>
<li>اضافه کردن {drinkers} قاشق چای و {0.5 * drinkers} قاشق ادویه.</li>
</ol>
);
}
وقتی ورودی drinkers={2} را به کامپوننت بالا میدهید، همیشه خروجی حاوی عدد ۲ پیمانه آب است. کامپوننتها درست مثل دستور پخت غذا هستند؛ اگر وسط آشپزی مواد عجیب و ناخوانده به قابلمه اضافه نکنید، طعم غذا همیشه یکسان خواهد بود.
فرایند رندر شدن در ریاکت باید مثل یک معبد، کاملاً پاک و دستنخورده باقی بماند. کامپوننتها فقط باید ظاهر را محاسبه کنند و برگردانند. آنها حق ندارند متغیرهایی را که بیرون از بدنه رندر وجود داشتهاند، دستکاری کنند.
بیایید یک کامپوننت ناپاک (Impure) را کالبدشکافی کنیم تا فاجعه را ببینید:
// 🔴 اشتباه بزرگ: تعریف متغیر در اسکوپ بیرونی
let guest = 0;
function Cup() {
// فاجعه: دستکاری متغیری که قبل از رندر وجود داشته است!
guest = guest + 1;
return <h2>فنجان چای برای مهمان شماره {guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}
چرا این کد خطرناک است؟ چون این کامپوننت دارد متغیر بیرون از خود را دستکاری میکند. اگر این کامپوننت چندین بار رندر شود، هر بار یک ظاهر متفاوت تولید میکند! از آن بدتر، اگر کامپوننت دیگری در صفحه بخواهد متغیر guest را بخواند، خروجیاش کاملاً وابسته به زمان رندر شدنِ این کامپوننت خواهد بود. این یعنی رفتار غیرقابلپیشبینی!
برای اصلاح این کامپوننت، کافی است متغیر را به عنوان یک پروپ (Prop) به درون کامپوننت بفرستیم تا کامپوننت دوباره پاک و قانونمند شود:
// اصلاح شده و پاک
function Cup({ guest }) {
return <h2>فنجان چای برای مهمان شماره {guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
حالا ظاهر خروجی کامپوننت فقط و فقط به پروپی که به آن پاس داده شده بستگی دارد. در ریاکت نباید انتظار داشته باشید که کامپوننتها با ترتیب خاصی رندر شوند؛ رندر شدن مثل امتحان مدرسه است، هر کامپوننت باید برگه خودش را بنویسد و کاری به بقیه نداشته باشد!
در ریاکت سه نوع ورودی وجود دارد که در زمان رندر خوانده میشوند: Props، State و Context. شما باید با هر سه ورودی مانند اشیاء «فقطخواندنی» (Read-Only) رفتار کنید.
برای اینکه در زمان برنامهنویسی متوجه اشتباهات و دستکاریهای ناخواسته متغیرها شوید، ریاکت ابزاری به نام Strict Mode دارد. در محیط توسعه (Development)، ریاکت تابع هر کامپوننت را صراحتاً ۲ بار اجرا میکند.
اگر تابع شما پاک باشد، دو بار اجرا شدنش هیچ چیزی را تغییر نمیدهد (مثل اینکه را دو بار حل کنید). اما در مثال فنجانهای ناپاک قبلی، اجرای دوبارۀ تابع باعث میشد شماره مهمانها به جای ۱ و ۲ و ۳، به صورت تکاندهنده و غلطِ Guest #2، Guest #4 و Guest #6 چاپ شود! این ابزار به شما کمک میکند تا پیش از بالا آمدن سایت روی سرور، باگ را شکار کنید.
💡 نکته: ابزار Strict Mode در نسخه نهایی و آنلاین سایت (Production) هیچ اثری ندارد و سرعت برنامه کاربران شما را به هیچ عنوان کم نمیکند. برای فعالسازی آن کافی است کامپوننت ریشه را در تگ
<React.StrictMode>بستهبندی کنید.
تغییر دادن متغیرهای بیرونی گناه کبیره است، اما تغییر دادن متغیرهایی که در همان لحظه و درون بدنه خود کامپوننت ساخته شدهاند، چطور؟
export default function TeaGathering() {
const cups = []; // این آرایه در همین لحظه رندر ساخته شده است
for (let i = 1; i <= 12; i++) {
cups.push(<Cup key={i} guest={i} />); // تغییر کاملاً مجاز است
}
return cups;
}
این کد کاملاً پاک و بدون اشکال است! چرا؟ چون آرایه cups بیرون از تابع وجود نداشته است. این آرایه در هر بار رندر از نو ساخته میشود و هیچ کد و کامپوننت دیگری در تمام پروژه از این تغییر باخبر نخواهد شد. به این رفتار «جهش محلی» (Local Mutation) میگویند که مثل یک راز کوچک و بیخطر درون کامپوننت شما مخفی میماند.
در نهایت یک اپلیکیشن وب باید بتواند چیزهایی را تغییر دهد؛ فرمها را ثبت کند، انیمیشن اجرا کند یا دادههای دیتابیس را بهروزرسانی کند. این تغییرات که در جریان رندر شدن نقشی ندارند، اثرات جانبی (Side Effects) نام دارند.
در ریاکت، اثرات جانبی معمولاً در دو جا خانه دارند:
مدیریتکنندههای رویداد (Event Handlers): توابعی مثل کلیک کردن روی یک دکمه یا تغییر یک فیلد ورودی. این توابع با اینکه داخل کامپوننت شما تعریف میشوند، اما در زمان رندر شدن اجرا نمیشوند (بلکه منتظر رفتار کاربر میمانند)؛ بنابراین نیازی نیست پاک باشند.
هوک useEffect: اگر تمام راهها را رفتید و هیچ Event Handler مناسبی برای کار خود پیدا نکردید، میتوانید آن اثر جانبی را درون useEffect قرار دهید. این هوک به ریاکت دستور میدهد که ابتدا رندر صفحه را تمام کند و سپس در امنترین زمان ممکن، کدهای جانبی شما را اجرا کند. (البته این روش باید آخرین سنگر شما باشد).
کامپوننتها باید کاملاً پاک باشند: سرشان به کار خودشان باشد و با ورودی یکسان، ظاهر (JSX) کاملاً یکسانی تولید کنند.
فاز رندر کامپوننتها نباید به ترتیب یا زمان اجرای بقیه کامپوننتها وابسته باشد.
هرگز ورودیهای کامپوننت یعنی Props، State و Context را مستقیماً دستکاری (Mutate) نکنید. برای تغییر در صفحه، از اکشنهای تغییر State استفاده کنید.
تغییر دادن متغیرهای محلی که درون خود تابع در همان لحظه ساخته شدهاند (Local Mutation) کاملاً مجاز و امن است.
برای انجام کارهای غیرپاک و تغییرات محیطی، از Event Handlerها یا در نهایت از هوک useEffect استفاده کنید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn