Fragment Refs: کنترل گروهی از عناصر DOM بدون عنصر والد اضافه

دومین قابلیتی که در React 19.3 (سپتامبر ۲۰۲۶) پایدار شد، Fragment Refs است. این قابلیت همان نسخه Experimental قبلی است و در نسخه ۱۹٫۳ به‌طور رسمی در دسترس قرار گرفته. مثل ViewTransition در درس قبل، اگر پروژه‌تان روی نسخه‌ای قدیمی‌تر است، باید ابتدا react و react-dom را به ۱۹٫۳ یا بالاتر ارتقا دهید.

یادآوری: Fragment چه مشکلی را حل می‌کرد

در درس ۱-۴ دیدیم که Fragment اجازه می‌دهد چند عنصر را گروه‌بندی کنیم، بدون اینکه یک div اضافه در DOM ساخته شود. اما همین مزیت یک محدودیت هم داشت: چون هیچ عنصر والدی وجود ندارد، هیچ نقطه‌ای هم برای گرفتن یک Ref از «کل گروه» نبود.

مشکل: کار با گروهی از عناصر هم‌سطح

فرض کنید کامپوننتی لیستی از عنوان‌ها را رندر می‌کند و ما می‌خواهیم از بیرون، روی همه آن‌ها یک Event Listener بگذاریم، یا بفهمیم کی وارد صفحه نمایش می‌شوند، یا فوکوس را به اولین‌شان ببریم. دو راه معمول، هر دو ایراد دارند:

  • افزودن یک div فقط برای نگه‌داشتن Ref: ممکن است استایل یا چیدمان (مثلاً Flex یا Grid در والد) را به‌هم بریزد.
  • گرفتن Ref از خود فرزندان: طبق چیزی که در درس ۷-۵ دیدیم، فقط وقتی ممکن است که آن کامپوننت ref را به یک عنصر بدهد. اگر کامپوننت از یک کتابخانه بیرونی می‌آید، تغییر دادنش معمولاً ممکن نیست.

راه‌حل: Ref روی یک Fragment صریح

jsx
import { Fragment, useRef, useEffect } from "react";

function Posts({ posts }) {
  const fragmentRef = useRef(null);

  useEffect(() => {
    const fragmentInstance = fragmentRef.current;
    fragmentInstance.focus();
  }, []);

  return (
    <Fragment ref={fragmentRef}>
      {posts.map((post) => (
        <h3 key={post.id} tabIndex={0}>{post.title}</h3>
      ))}
    </Fragment>
  );
}

مقداری که در ref.current قرار می‌گیرد یک FragmentInstance است. این آبجکت روی عناصر DOM فرزند Fragment به‌صورت گروهی کار می‌کند، بدون اینکه ساختار DOM تغییری کند: همچنان هیچ عنصر اضافه‌ای ساخته نمی‌شود.

باید Fragment صریح نوشت

نحو کوتاه <>...</> نمی‌تواند Ref بگیرد؛ همان‌طور که نمی‌تواند key هم بگیرد (درس ۱-۴). برای استفاده از Fragment Ref باید Fragment را import کنید و صریحاً بنویسید <Fragment ref={...}>.

FragmentInstance یک عنصر DOM نیست

نکته مهم این است که FragmentInstance یک عنصر واقعی DOM نیست. فقط مجموعه محدودی از متدهای پرکاربرد DOM را ارائه می‌دهد که روی گروه فرزندان عمل می‌کنند. بنابراین مثلاً نمی‌توان از آن style گرفت یا children خواند.

متدهای FragmentInstance

گروه متدها کاربرد
رویدادها addEventListener، removeEventListener، dispatchEvent مدیریت رویداد برای فرزندان سطح اول
فوکوس focus، focusLast، blur جابه‌جایی فوکوس بین فرزندان تودرتو، به‌صورت عمقی (Depth-first)
مشاهده‌گرها observeUsing، unobserveUsing اتصال یک IntersectionObserver یا ResizeObserver
اندازه و موقعیت getClientRects، getRootNode، compareDocumentPosition، scrollIntoView اندازه‌گیری و اسکرول بر اساس فرزندان سطح اول

تفاوت «سطح اول» و «تودرتو» در این جدول عمدی است: بیشتر متدها (مثل رویدادها و اندازه‌گیری) روی عناصر DOM سطح اول Fragment کار می‌کنند، یعنی همان‌هایی که Fragment مستقیماً رندر می‌کند، ولی متدهای فوکوس، برای پیدا کردن اولین یا آخرین عنصر قابل‌فوکوس، داخل فرزندان تودرتو هم می‌روند.

مثال کاربردی: کامپوننت InView

یک الگوی رایج، کامپوننتی است که می‌فهمد گروهی از فرزندانش وارد صفحه نمایش شده‌اند یا نه، بدون اینکه خودش یک عنصر DOM اضافه کند و بدون نیاز به اینکه فرزندان ref بپذیرند:

jsx
function InView({ onChange, children }) {
  const fragmentRef = useRef(null);

  useEffect(() => {
    const fragment = fragmentRef.current;

    const observer = new IntersectionObserver((entries) => {
      onChange(entries.some((entry) => entry.isIntersecting));
    });

    fragment.observeUsing(observer);

    return () => {
      fragment.unobserveUsing(observer);
    };
  }, [onChange]);

  return <Fragment ref={fragmentRef}>{children}</Fragment>;
}

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

زمان درست استفاده از Ref

مثل هر Ref دیگری (درس ۷-۲)، fragmentRef.current پیش از Commit مقدار null دارد. پس خواندن و استفاده از آن باید داخل Effect یا Event Handler انجام شود، نه در بدنه اصلی کامپوننت در حین Render.

چه زمانی از آن استفاده کنیم

Fragment Ref برای موقعیتی است که لازم است رفتار یا اندازه‌گیری‌ای روی یک گروه از عناصر هم‌سطح اعمال شود، و ساختن یک عنصر والد اضافه، یا تغییر کامپوننت فرزند، مناسب یا ممکن نیست. اگر فقط با یک عنصر مشخص کار دارید، همان Ref معمولی کافی است. و مثل بقیه Refها، اگر چیزی را می‌توان با Prop و State توصیفی توضیح داد، آن راه ترجیح دارد؛ Fragment Ref ابزار «در رفتن از مدل توصیفی» برای کارهای دستوری مثل فوکوس، مشاهده و اندازه‌گیری است.

چرا این درس مثال قابل اجرا ندارد

مثل درس قبل، اجرای این مثال‌ها به React 19.3 یا بالاتر نیاز دارد و نتیجه برخی از آن‌ها (مثل مشاهده ورود به صفحه نمایش) فقط در یک صفحه واقعی با اسکرول قابل دیدن است. این کدها را در پروژه خودتان با نسخه مناسب اجرا کنید.

جمع‌بندی

Fragment Refs از React 19.3 پایدار شده‌اند. با دادن ref به یک Fragment صریح، یک FragmentInstance به دست می‌آید که مجموعه‌ای محدود از متدهای DOM (رویدادها، فوکوس، مشاهده‌گرها و اندازه‌گیری) را روی گروه فرزندان فراهم می‌کند، بدون افزودن هیچ عنصری به DOM و بدون تغییر در کامپوننت‌های فرزند. نحو کوتاه <> نمی‌تواند Ref بگیرد، و FragmentInstance یک عنصر کامل DOM نیست. با این درس، APIهای مدرن این فصل به پایان می‌رسد و در ادامه به آزمون فصل ۲۳ می‌رویم.