الگوهای رایج استفاده از Ref در برنامه‌های واقعی

در طول این فصل، با جنبه‌های مختلف Ref آشنا شدیم: نگهداری مقادیر Mutable، دسترسی به عناصر DOM، Callback Ref، ref به‌عنوان Prop، و useImperativeHandle. در این درس، چند الگوی عملی و پرتکرار را مرور می‌کنیم که معمولاً در پروژه‌های واقعی React با Ref پیاده‌سازی می‌شوند.

نگه‌داشتن آخرین مقدار یک Prop یا State، بدون رندر مجدد

گاهی داخل یک Effect یا یک Callback غیرهمزمان، نیاز داریم به آخرین مقدار یک متغیر دسترسی داشته باشیم، بدون این‌که آن Effect به‌خاطر تغییر آن مقدار، دوباره اجرا شود. یک الگوی رایج، نگه‌داشتن آخرین مقدار در یک Ref، در کنار State اصلی است:

function ChatBox({ draft }) {
  const latestDraftRef = useRef(draft);

  useEffect(() => {
    latestDraftRef.current = draft;
  }, [draft]);

  useEffect(() => {
    const id = setInterval(() => {
      console.log("آخرین پیش‌نویس:", latestDraftRef.current);
    }, 5000);
    return () => clearInterval(id);
  }, []);

  return null;
}

در این الگو، تایمر داخلی همیشه به latestDraftRef.current دسترسی دارد که با هر تغییر draft به‌روزرسانی می‌شود؛ بدون این‌که خود تایمر مجبور باشد با هر تغییر draft از نو ساخته شود.

جلوگیری از اجرای دوباره یک منطق (مثلاً در Development Mode)

همان‌طور که در فصل ششم اشاره شد، React در حالت توسعه ممکن است یک Effect را دوبار اجرا کند تا مشکلات Cleanup را آشکار کند. برای عملیاتی که واقعاً باید فقط یک‌بار در طول عمر برنامه انجام شوند (نه یک‌بار به ازای هر Mount)، گاهی از یک Ref به‌عنوان پرچم استفاده می‌شود:

function AnalyticsInit() {
  const initializedRef = useRef(false);

  useEffect(() => {
    if (initializedRef.current) return;
    initializedRef.current = true;

    console.log("راه‌اندازی اولیه انجام شد");
  }, []);

  return null;
}

این الگو باید با احتیاط و فقط در موارد واقعاً لازم استفاده شود، چون می‌تواند رفتار طبیعی Effectها را پنهان کند؛ در بیشتر موارد، بهتر است ابتدا بررسی شود که آیا خود Effect اصلاً نیاز به Cleanup مناسب دارد یا نه.

نگه‌داشتن مرجع چند عنصر در یک لیست

گاهی نیاز داریم به چند عنصر DOM در یک لیست، مثلاً برای اسکرول کردن به یک آیتم خاص، دسترسی داشته باشیم. چون تعداد آیتم‌ها پویا است، معمولاً یک Map داخل یک Ref نگه داشته می‌شود:

function ItemList({ items }) {
  const itemRefs = useRef(new Map());

  function scrollToItem(id) {
    const node = itemRefs.current.get(id);
    node?.scrollIntoView({ behavior: "smooth" });
  }

  return (
    <ul>
      {items.map((item) => (
        <li
          key={item.id}
          ref={(node) => {
            if (node) {
              itemRefs.current.set(item.id, node);
            } else {
              itemRefs.current.delete(item.id);
            }
          }}
        >
          {item.name}
        </li>
      ))}
    </ul>
  );
}

در این الگو، از Callback Ref که در درس چهارم این فصل دیدیم استفاده شده تا هر عنصر، هنگام اتصال، در Map ثبت شود و هنگام جدا شدن، از آن حذف شود.

یک اصل کلی برای جمع‌بندی این فصل

در همه این الگوها، یک اصل مشترک تکرار می‌شود: Ref برای اطلاعاتی استفاده می‌شود که خارج از جریان توصیفی رندر React زندگی می‌کنند؛ چیزهایی که خواندن یا نوشتنشان، خودش نباید بخشی از منطق نمایشی JSX باشد. هر وقت با خودتان فکر کردید «این مقدار باید همین الان و بدون تأخیر در دسترس باشد، اما نیازی نیست چیزی روی صفحه بر اساس آن تغییر کند»، احتمالاً Ref ابزار مناسبی است.

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

نگه‌داشتن آخرین مقدار یک Prop در Ref
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

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