انتقال ref ها (Forwarding Refs) در React

انتقال ref تکنیکی است برای منتقل کردن یک ref به واسطۀ یک کامپوننت به یکی از child های آن. این کار معمولا برای اکثر کامپوننت‌های برنامه لازم نیست. با این حال، می‌تواند برای برخی انواع کامپوننت‌ها کارآمد باشد، مخصوصا در کتابخانه‌های کامپوننت‌های قابل استفادۀ مجدد. اکثر سناریوهای رایج در ادامه آورده شده‌اند.

انتقال ref ها به کامپوننت‌های DOM

یک کامپوننت FancyButton را که المان DOM محلی button را رندر می‌کند در نظر بگیرید:

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}

کامپوننت‌های React جزئیات پیاده سازی خود را پنهان می‌کنند، از جمله خروجی رندر شدۀ آنها. کامپوننت‌های دیگری که از FancyButton استفاده می‌کنند معمولا نیاز به گرفتن ref در المان DOM داخلی button ندارند. این خوب است چون از تکیۀ زیاد کامپوننت‌ها بر ساختار DOM یکدیگر جلوگیری می‌کند.

اگرچه این محدودسازی برای کامپوننت‌های در سطح برنامه مانند FeedStory یا Comment مطلوب است، می‌تواند برای کامپوننت‌های leaf بسیار قابل استفادۀ مجدد مانند FancyButton یا MyTextInput نامناسب باشد. این کامپوننت‌ها تمایل دارند در سراسر برنامه همان گونه‌ای استفاده شوند که از DOM معمولی button و input استفاده می‌شود، و دسترسی به DOM node های آنها ممکن است برای مدیریت تمرکز، انتخاب و انیمیشن‌ها اجتناب ناپذیر باشد.

انتقال ref یک ویژگی انتخابی است که به برخی کامپوننت‌ها اجازه می‌دهد ref رسیده به آنها را بگیرند و آن را به پایین‌تر یعنی به child منتقل کنند.

در مثال زیر FancyButton از React.forwardRef برای گرفتن ref منتقل شده به آن استفاده می‌کند و سپس آن را به button DOM که آن را رندر می‌کند، منتقل می‌کند:

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

با این روش، کامپوننت‌های استفاده کنندۀ FancyButton می‌توانند یک ref به button DOM node قرار گرفته در پایین داده و در صورت نیاز به آن دسترسی داشته باشند، درست مانند این که خودشان مستقیما از DOM button استفاده کنند.

در این قسمت توضیح گام به گامی از اینکه در مثال بالا چه چیزهایی رخ داده، آورده شده است:

  1. ما با فراخوانی createRef و اختصاص دادن آن به یک متغیر ref، یک ref ری‌اکت ساختیم.
  2. ما ref خودمان را با تعیین کردن آن به عنوان یک ویژگی JSX، به پایین یعنی به to <FancyButton ref={ref}> منتقل کردیم.
  3. ری‌اکت ref را به تابع (props, ref) => … در داخل forwardRef به عنوان آرگومان دوم منتقل می‌کند.
  4. ما این آرگومان ref را با تعیین کردن آن به عنوان یک ویژگی JSX، به پایین به <button ref={ref}> منتقل کردیم.
  5. وقتی ref متصل شد، current متوجه <button> DOM node خواهد شد.

نکته: آرگومان دوم ref فقط وقتی وجود دارد که شما با فراخوانی React.forwardRef یک کامپوننت تعریف کنید. تابع معمولی یا کامپوننت‌های class آرگومان ref را دریافت نمی‌کنند و ref در prop ها هم قابل دسترسی نیست. انتقال ref فقط به کامپوننت‌های DOM محدود نمی‌شود. شما می‌توانید آنها را به کامپوننت‌های class هم منتقل کنید.

نکته‌ای برای نگه دارنده‌های کتابخانۀ کامپوننت

وقتی در یک کتابخانۀ کامپوننت شروع به استفاده از forwardRef کنید، باید با آن به عنوان یک breaking change رفتار کنید و یک نسخۀ بزرگ جدید از کتابخانۀ‌تان منتشر کنید. این به این خاطر است که کتابخانۀ شما احتمالا یک رفتار متفاوت مشهود دارد (مانند چیزی که ref ها به آن اختصاص می‌یابند، و نوعی که صادر می‌شوند) و این می‌تواند در برنامه‌ها و کتابخانه‌های دیگر که به رفتارهای قدیمی‌تر وابسته هستند، مشکل ایجاد کند.

اعمال مشروط React.forwardRef وقتی وجود دارد نیز به همان دلایل توصیه نمی‌شود: باعث تغییر رفتار کتابخانۀ شما شده و می‌تواند برنامه‌های کاربر را وقتی خود React را آپگرید می‌کنند، با اشکال مواجه کند.

انتقال ref ها به کامپوننت‌های مرتبۀ بالاتر

این تکنیک می‌تواند برای کامپوننت‌های مرتبۀ بالاتر (یا HOC) کارآمد باشد. بیایید با یک مثال از HOC که prop های کامپوننت را به کنسول log می‌کند، شروع کنیم:

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
}

logProps تمام prop ها را به واسطۀ کامپوننتی که در بر دارد منتقل می‌کند، در نتیجه خروجی رندر شده همان خواهد بود. برای مثال ما می‌توانیم از این HOC برای log کردن همۀ prop هایی که به کامپوننت fancy button منتقل می‌شود، استفاده کنیم:

class FancyButton extends React.Component {
  focus() {
    // ...
  }

  // ...
}

// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);

یک هشدار در رابطه با مثال بالا وجود دارد: ref ها منتقل نمی‌شوند. علتش این است که ref یک prop نیست. مانند key، به شکل متفاوتی توسط React کنترل می‌شود. اگر شما یک ref به HOC اضافه کنید، ref به بیرونی‌ترین container کامپوننت مراجعه می‌کند، نه کامپوننت بسته بندی شده.

این یعنی ref های مورد نظر ما برای کامپوننت FancyButton در واقع به کامپوننت LogProps متصل هستند:

import FancyButton from './FancyButton';

const ref = React.createRef();

// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;

خوشبختانه ما می‌توانیم با استفاده از React.forwardRef API به طور صریح ref ها را به کامپوننت داخلی FancyButton منتقل کنیم. React.forwardRef یک تابع رندر را می‌پذیرد که پارامترها prop ها و ref ها را دریافت کرده و React node برمی‌گرداند. برای مثال:

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // Assign the custom prop "forwardedRef" as a ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // Note the second param "ref" provided by React.forwardRef.
  // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
  // And it can then be attached to the Component.
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

نمایش یک نام دلخواه در DevTools

React.forwardRef یک تابع رندر را می‌پذیرد. React DevTools از این تابع برای تعیین این که چه چیزی برای کامپوننت انتقال ref نمایش داده شود، استفاده می‌کند.

برای مثال کامپوننت انتقال به صورت ForwardRef در DevTools ظاهر می‌شود:

const WrappedComponent = React.forwardRef((props, ref) => {
  return <LogProps {...props} forwardedRef={ref} />;
});

اگر شما برای تابع رندر نامی تعیین کنید، DevTools نیز آن نام را در بر خواهد گرفت (برای مثال ForwardRef(myFunction)):

const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }
);

حتی شما می‌توانید مشخصۀ displayName تابع را تنظیم کنید تا کامپوننتی که بسته بندی کردید را در بر بگیرد:

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  // Give this component a more helpful display name in DevTools.
  // e.g. "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}
1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5٫00 out of 5)
Loading...
counter customizable free hit