useRef و مقادیر Mutable

در فصل‌های قبلی، چند بار به‌طور مختصر از useRef استفاده کردیم؛ برای دسترسی به یک عنصر DOM در Uncontrolled Components و برای نگه‌داشتن مقادیری مانند شناسه یک تایمر. در این فصل، به‌طور کامل به سراغ useRef می‌رویم و در این درس اول، با مفهوم پایه‌ای آن، یعنی نگهداری یک مقدار Mutable، آشنا می‌شویم.

یادآوری مشکل State: هر تغییر باعث رندر مجدد می‌شود

در فصل سوم دیدیم که useState برای نگهداری مقادیری طراحی شده که تغییرشان باید روی رابط کاربری اثر بگذارد؛ به همین دلیل، هر فراخوانی set، یک رندر مجدد را درخواست می‌دهد. اما گاهی نیاز داریم مقداری را بین رندرهای مختلف حفظ کنیم، بدون این‌که تغییر آن، باعث رندر مجدد شود؛ مثلاً شناسه یک تایمر، یا تعداد دفعاتی که یک تابع فراخوانی شده، بدون نیاز به نمایش آن روی صفحه.

useRef: مقداری پایدار و بدون رندر مجدد

useRef یک آبجکت بازمی‌گرداند که دقیقاً یک ویژگی به نام current دارد. این آبجکت، بین رندرهای مختلف کامپوننت، همیشه همان آبجکت باقی می‌ماند؛ و تغییر current، برخلاف تغییر State، هیچ رندر مجددی را درخواست نمی‌دهد.

function ClickCounter() {
  const clickCountRef = useRef(0);

  function handleClick() {
    clickCountRef.current = clickCountRef.current + 1;
    console.log("تعداد کلیک:", clickCountRef.current);
  }

  return <button onClick={handleClick}>کلیک کن</button>;
}

در این کد، با هر کلیک، مقدار clickCountRef.current افزایش پیدا می‌کند و در کنسول چاپ می‌شود، اما هیچ رندر مجددی رخ نمی‌دهد؛ اگر بخواهیم این عدد را روی صفحه هم نمایش دهیم، باید آن را در State نگه داریم، نه در Ref.

تفاوت اصلی با یک متغیر معمولی

ممکن است این سؤال پیش بیاید که چرا نمی‌توان به‌جای useRef، فقط یک متغیر معمولی داخل تابع کامپوننت تعریف کرد. تفاوت اصلی این است که یک متغیر معمولی، در هر رندر از نو ساخته می‌شود و مقدارش از دست می‌رود؛ اما useRef، مقدار current را بین رندرهای مختلف حفظ می‌کند، دقیقاً مانند State، با این تفاوت که تغییرش رندر مجدد ایجاد نمی‌کند.

function BadCounter() {
  let count = 0; // در هر رندر دوباره صفر می‌شود

  function handleClick() {
    count = count + 1;
    console.log(count); // همیشه ۱ چاپ می‌شود
  }

  return <button onClick={handleClick}>کلیک</button>;
}

خواندن و نوشتن Ref در حین رندر مجاز نیست

یک قانون مهم درباره Refها این است که نباید مقدار current را در حین محاسبه خروجی JSX (یعنی مستقیماً در بدنه تابع کامپوننت، در طول رندر) خواند یا نوشت. Ref برای اطلاعاتی طراحی شده که خارج از جریان رندر React زندگی می‌کند؛ خواندن یا نوشتن آن در حین رندر، رفتار کامپوننت را غیرقابل‌پیش‌بینی می‌کند. Refها معمولاً باید فقط داخل Event Handlerها یا Effectها خوانده و نوشته شوند.

چه زمانی از Ref استفاده کنیم، نه State

یک قاعده کلی مفید: اگر تغییر یک مقدار باید روی چیزی که کاربر می‌بیند اثر بگذارد، آن مقدار باید State باشد. اگر تغییر آن مقدار فقط برای منطق داخلی کامپوننت لازم است و نیازی به بازتاب روی صفحه ندارد (مانند شناسه یک تایمر، مرجع یک عنصر DOM، یا شمارش تعداد رندرها برای اهداف دیباگ)، Ref انتخاب مناسب‌تری است.

مثال قابل اجرا

مقایسه رفتار useRef و useState
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

useRef آبجکتی با ویژگی current بازمی‌گرداند که بین رندرهای مختلف پایدار می‌ماند، اما تغییر آن، برخلاف State، رندر مجدد ایجاد نمی‌کند. Ref برای مقادیری مناسب است که فقط برای منطق داخلی لازم‌اند و نیازی نیست روی رابط کاربری منعکس شوند، و نباید در حین رندر خوانده یا نوشته شوند. در درس بعدی به کاربرد رایج دیگر Ref، یعنی دسترسی به عناصر DOM، می‌پردازیم.