Ref در برابر State: چه زمانی کدام را انتخاب کنیم

در دو درس قبلی این فصل، با useRef برای نگهداری مقادیر Mutable و برای دسترسی به عناصر DOM آشنا شدیم. اما چون هر دو ابزار، مقداری را بین رندرهای مختلف کامپوننت حفظ می‌کنند، ممکن است این سؤال پیش بیاید: دقیقاً کجا باید از Ref استفاده کرد و کجا از State؟ این درس به‌طور مستقیم به همین مقایسه می‌پردازد.

شباهت ظاهری: هر دو مقدار را بین رندرها حفظ می‌کنند

هم useState و هم useRef، یک ویژگی مشترک دارند: مقداری که نگه می‌دارند، با رندر مجدد کامپوننت از بین نمی‌رود؛ برخلاف یک متغیر معمولی که در هر رندر از نو ساخته می‌شود. همین شباهت، گاهی باعث سردرگمی می‌شود که کدام یک باید استفاده شود.

تفاوت اصلی اول: رندر مجدد

مهم‌ترین تفاوت این دو، رفتار آن‌ها نسبت به رندر مجدد است. تغییر State، همیشه باعث می‌شود React کامپوننت را دوباره رندر کند؛ اما تغییر current در یک Ref، هیچ رندر مجددی ایجاد نمی‌کند.

const [count, setCount] = useState(0);
setCount(count + 1); // باعث رندر مجدد می‌شود

const countRef = useRef(0);
countRef.current = countRef.current + 1; // هیچ رندری رخ نمی‌دهد

تفاوت اصلی دوم: Snapshot در برابر مقدار همیشه به‌روز

همان‌طور که در فصل پنجم دیدیم، مقدار State در طول یک رندر خاص، مانند یک عکس لحظه‌ای ثابت باقی می‌ماند؛ حتی اگر بلافاصله set فراخوانی شود. اما مقدار current یک Ref، همیشه بلافاصله و همان لحظه تغییر می‌کند، بدون نیاز به منتظر ماندن برای رندر بعدی:

function handleClick() {
  setCount(count + 1);
  console.log(count); // مقدار قدیمی

  countRef.current = countRef.current + 1;
  console.log(countRef.current); // مقدار جدید، همین الان
}

قاعده تصمیم‌گیری

با توجه به این دو تفاوت، می‌توان یک قاعده ساده برای انتخاب نوشت:

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

برای مثال، متن وارد‌شده در یک input که باید همان لحظه روی صفحه نمایش داده شود، باید State باشد. اما شناسه یک setInterval که فقط برای پاک کردن آن تایمر در Cleanup لازم است، باید Ref باشد؛ چون هیچ بخشی از رابط کاربری مستقیماً بر اساس آن شناسه تغییر نمی‌کند.

یک اشتباه رایج: استفاده از Ref به‌جای State برای صرفه‌جویی در رندر

گاهی توسعه‌دهندگان، برای «جلوگیری از رندرهای اضافه»، مقداری را که در واقع باید روی صفحه نمایش داده شود، به‌اشتباه در Ref نگه می‌دارند:

// نادرست
function Counter() {
  const countRef = useRef(0);

  function handleClick() {
    countRef.current += 1;
  }

  return <button onClick={handleClick}>{countRef.current}</button>;
}

در این کد، عدد نمایش‌داده‌شده روی دکمه هرگز به‌روزرسانی نمی‌شود، چون تغییر countRef.current هیچ رندر مجددی درخواست نمی‌دهد؛ React از تغییر مقدار باخبر نمی‌شود تا JSX را دوباره محاسبه کند. این دقیقاً همان مشکلی است که در فصل سوم، با استفاده از یک متغیر معمولی به‌جای useState دیدیم.

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

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

جمع‌بندی

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