تمرير المراجع

تمرير المراجع هو تقنية لتمرير مرجع ref تلقائيًّا من مكوّن إلى عناصره الأبناء. لا يكون هذا ضروريًّا بشكل نموذجي لمعظم مكوّنات التطبيق، ولكن قد يكون مفيدًا لبعض أنواع المكوّنات، خاصّة مكتبات المكوّنات القابلة لإعادة الاستخدام. سنتحدث في هذه الصفحة عن أشيع الحالات التي نحتاج فيها تمرير المراجع.

تمرير المراجع إلى مكونات DOM

فلنأخذ مثال عن المكوّن FancyButton والذي يُصيِّر عنصر button الأصلي في DOM:

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

تُخفي مكوّنات React تفاصيلها التنفيذية، وهذا يشمل الناتج المُصيَّر. هناك مكونات أخرى تستخدم المكوّن FancyButton عادة لا تحتاجً للحصول على مرجع لعنصر الزر buttonالداخلي. يكون هذا جيّدًا لأنّه يمنع المكوّنات من الاعتماد كثيرًا على بنية DOM للمكوّنات بعظها البعض.

قد يكون مثل هذا التغليف مرغوبًا في مكوّنات التطبيق مثل FeedStory أو Comment، ولكنّه قد يكون غير مناسب بالنسبة للمكوّنات القابلة لإعادة الاستخدام بكثرة مثل FancyButton أو MyTextInput. تميل هذه المكوّنات ليتم استخدامها في التطبيق بطريقة مماثلة لزر button وحقل الإدخال input في DOM الاعتيادي، وقد يكون الوصول إلى عقد DOM الخاصّة بها أمرًا لا بُدّ منه لإدارة التركيز، أو الاختيار، أو التحريكات.

تمرير المراجع ميزة اختيارية تسمح لبعض المكوّنات باستقبال مرجع ref وتمريره إلى المستويات الأدنى إلى المكوّنات الأبناء.

في المثال ادناه ، المكون FancyButton يستخدم React.forwardRef للحصول على المرجع ref المُمرَّر له، ومن ثمّ يُمرِّره إلى عنصر DOM الأصلي button الذي يُصيِّره:

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 الحصول على مرجع عقد DOM الأساسي button والوصول إليه عند الضرورة ، كما لو تم أستخدام عنصر button الأصلي بشكل مباشر.

وهذا شرح مُفصَّل خطوة بخطوة عمّا يحدث في المثال السابق أعلاه:

  1. نقوم بأنشاء مرجع ref عن طريق استدعاء React.createRef وتعيينه إلى المتغيّر ref.
  2. نمرر المرجع ref إلى المكوّن ‎<FancyButton ref={ref}>‎ عن طريق تحديده كخاصيّة JSX.
  3. تُمرِّر React المرجع ref إلى الدالة ‎(props, ref) => ...‎ الموجودة بداخل forwardRef كوسيط ثانٍ له.
  4. نُمرِّر هذا المرجع إلى الزر ‎‎<button ref={ref}>‎‎ عن طريق تحديده كخاصيّة JSX.
  5. عند ربط المرجع ستُشير ref.current إلى عقدة DOM الخاصّة بالزر ‎<button>‎.

ملاحظة

يكون الوسيط الثاني ref فقظ عندما يتم تعريف المكون من خلال استدعاء React.forwardRef. لا تستقبل الدوال الاعتيادية او مكونات الصنف الوسيط ref و لذلك فهو غير متاح في الخاصيات حتى.

تمرير المراجع ليس محدود فقظ لمكونات DOM. بل أنه بالأمكان تمرير المراجع إلى نُسَخ مكونات الصنف أيضًا.

ملاحظة لمشرفين مكتبات المكونات

عندما تبدأ باستخدام forwardRef في مكتبة مكوّناتك الخاصّة، فيجب عليك معاملتها كتغيير جذري وأطلاق إصدار رئيسي جديد من مكتبتك. وذلك لأنّه من الغالب أنّ مكتبتك لديها سلوك ملحوظ مختلف تمامًا (مثل الأشياء التي نُعيِّن إليها المراجع، والأنواع المستخرجة) وقد يُعطِّل هذا التطبيقات والمكتبات الأخرى التي تعتمد على السلوك القديم.

لا نوصي بتطبيق React.forwardRef بشكلٍ شرطي عند وجودها لنفس الأسباب، فهي تغيّر سلوك مكتبتك وقد تُعطِّل تطبيقات مستخدميك عند تحديثهم لمكتبة React بحد ذاتها.

تمرير المراجع في مكونات من المرتبة الأعلى

تكون هذه الطريقة مفيدة بشكل خاص مع مكونات من المرتبة الأعلى higher-order components (واختصارًا HOCs). فلنبدأ بمثال عن مكونات من المرتبة الأعلى والذي يعرض خاصيّات المكوّن في الكونسول:

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 كل الخاصيّات عبر المكوّن الذي يُغلِّفه، لذا سيكون الناتج المُصيَّر نفسه. فمثلًا نستطيع استخدام هذا المكوّن من المرتبة الأعلى لعرض جميع الخاصيّات المُمرَّرة إلى المكوّن FancyButton:

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

  // ...
}

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

هنالك شيء واحد يجب الانتباه له في المثال أعلاه ، وهو أن المراجع لن يتم تمريرها. وذلك لأنّ ref ليست خاصيّة. مثل خاصية المفتاح key مثلًا، حيث تُعامِلها React بشكلٍ مختلف. إن أضفت مرجعًا إلى مكوّن من المرتبة الأعلى فأنه سيُشير إلى المكوّن الخارجي الحاوي وليس المكوّن المُغلّف.

هذا يعني أنّ المراجع المُخصَّصة من أجل المكوّن 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}
/>;

لحسن الحظ نستطيع تمرير المراجع إلى المكوّن الداخلي FancyButton باستخدام واجهة برمجة التطبيقات React.forwardRef. والتي تقبل دالة تصيير تستقبل مُعامِلات للخاصيّات props والمرجع ref وتُعيد عقدة React. على سبيل المثال:

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 هذه الدالة لتحديد ما سيُعرَض من أجل المكون مُمَرِر المرجع.

على سبيل المثال ، سيظهر المكوّن التالي بالاسم ”ForwardRef” في أدوات التطوير:

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

إن سميت دالة التصيير فستُضمّن أدوات التطوير اسمها (على نحو ‎”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);
}