استخدام المراجع مع DOM في React

من موسوعة حسوب

تُزوّدنا المراجع بطريقة للوصول إلى عقد DOM أو عناصر React المُنشأة باستخدام التابع render.

تكون الخاصيّات في تدفّق بيانات React النموذجي هي الطريقة الوحيدة التي يتواصل بها المُكوِّن الأب مع مُكوِّناته الأبناء، ولتعديل الابن نُعيد تصييره باستخدام الخاصيّات الجديدة. على الرغم من ذلك هنالك حالات نحتاج فيها بشكلٍ إجباري إلى تعديل المُكوِّنات الأبناء خارج هذا التدفّق النموذجي. لتعديل المُكوِّن الابن يجب أن يكون نسخة (instance) من مُكوِّن React أو عنصر DOM. ولكلتا الحالتين تُعطينا React خطة للالتفاف حول هذا الموضوع.

متى نستخدم المراجع (Refs)

هنالك بعض الحالات المناسبة لاستخدام المراجع (refs) وهي:

  • إدارة التركيز على العناصر (focus)، واختيار النصوص، والتحكم بتشغيل الوسائط.
  • إطلاق التحريكات الإجباريّة.
  • التكامل مع مكتبات طرف ثالث تتعامل مع DOM.

تجنّب استخدام المراجع لأي شيء يُمكِن فعله بشكلٍ إلزامي. فمثلًا بدلًا من تعريض التوابع open()‎ و close()‎ في مُكوِّن مربّع الحوار Dialog، مرِّر الخاصيّة isOpen له.

لا تُفرِط في استخدام المراجع

ربّما تكون رغبتك الأولى لاستخدام المراجع هي تحقيق كل ما تُريده في تطبيقك. إن كانت هذه حالتك فخُذ لحظة للتفكير حول المكان المُلائم لوضع الحالة في التسلسل الهرمي للمُكوِّنات. يتضح عادةً أنّ المكان المناسب لوضع الحالة هو في المستويات العليا من التسلسل الهرمي للمُكوِّنات. انظر إلى قسم رفع الحالات المشتركة للمستوى الأعلى للمزيد من المعلومات.

ملاحظة: حدّثنا الأمثلة التالية لكي تستخدم واجهة برمجة التطبيق React.createRef()‎ التي قُدِّمَت في إصدار React 16.3. إن كُنتَ تستخدم إصدار أقدم من React نُوصي باستخدام ردود نداء المراجع بدلًا من ذلك.

إنشاء المراجع

تُنشَأ المراجع باستخدام React.createRef()‎ وتُربَط إلى عناصر React عبر الخاصيّة ref. تُعيَّن المراجع إلى نسخة من الخاصيّة عند بناء المُكوِّنات لكي نرجع إليها عبر المُكوِّن:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

الوصول للمراجع

عند تمرير المرجع إلى عنصر في التابع render، يُصبِح المرجع إلى العقدة قابلًا للوصول باستخدام الخاصّية current للمرجع:

const node = this.myRef.current;

تختلف قيمة المرجع بناءً على نوع العقدة:

  • عند استخدام الخاصيّة ref على عنصر HTML، تستقبل هذه الخاصيّة ref المُنشَأة في الدالة البانية باستخدام React.createRef()‎ عنصر DOM كخاصيّة حاليّة current.
  • عند استخدام الخاصيّة ref على مُكوِّن صنف مُخصَّص، يستقبل الكائن ref نسخة من المُكوِّن كخاصيّة حاليّة current.
  • لا يُمكنك استخدام الخاصيّة ref على المُكوِّنات المُعرَّفة كدوال لأنّها لا تملك نُسَخ (instances).

يُوضِّح المثال التالي الفروقات.

إضافة مرجع إلى عنصر DOM

تستخدم هذه الشيفرة الخاصيّة ref لتخزين مرجع إلى عقدة DOM:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
	// إنشاء مرجع لتخزين عقدة DOM وهي textInput
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
	// التركيز على الحقل النصي باستخدام DOM API
	// ملاحظة: نحن نصل إلى الخاصيّة current للحصول على عقدة DOM
    this.textInput.current.focus();
  }

  render() {
	// إخبار React أننا نريد ربط مرجع العنصر input
	// مع textInput التي أنشأناها في الدالة البانية
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />

        <input
          type="button"
          value="ركز على الحقل النصي"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

ستُعيِّن React الخاصيّة current إلى عنصر DOM عندما وصل المُكوِّن (mount)، وتُعيِّنها إلى القيمة null عند فصل المُكوِّن (unmount). تحصل تحديثات ref قبل خُطافات دورة حياة المُكوِّن componentDidMount أو componentDidUpdate.

إضافة مرجع إلى مُكوِّن الصنف

إن أردنا تغليف المُكوِّن CustomTextInput السّابق لمُحاكاة النقر عليها فورًا بعد الوصل (mounting)، فبإمكاننا استخدام المرجع ref للوصول إلى حقل الإدخال المُخصَّص واستدعاء تابعه focusTextInput بشكل يدوي:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

لاحظ أنّ هذا يعمل فقط إن كان المُكوِّن CustomTextInput مُعرَّفًا كصنف:

class CustomTextInput extends React.Component {
  // ...
}

المراجع والمكونات الدالية

لا يُمكنِك استخدام الخاصيّة ref على المُكوِّنات الداليّة لأنّها لا تمتلك نُسَخ:

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
	// لن يعمل هذا
    return (
      <MyFunctionalComponent ref={this.textInput} />
    );
  }
}

إذا أردت السماح للناس بتمرير مرجع لمكون دالي، فعليك استخدام forwardRef (ربما مع useImperativeHandle)، أو يمكنك تحويل المكون إلى صنف. على أيّة حال تستطيع استخدام الخاصيّة ref بداخل المُكوِّن المُعرَّف كدالة طالما تُشير إلى عنصر DOM أو مُكوِّن مُعرَّف كصنف:

function CustomTextInput(props) {
  // يجب تعريف textInput هنا لكي يستطيع المرجع الرجوع إليها
  const textInput = useRef(null);
  
  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="ركز على الحقل النصي"
        onClick={handleClick}
      />
    </div>
  );
}

تعريض مراجع DOM للمكون الأب

قد ترغب في بعض الحالات النادرة الوصول إلى عقدة DOM للمُكوِّن الابن من خلال المُكوِّن الأب. لا يُفضَّل فعل ذلك بشكل عام لأنّه قد يخرق تغليف المُكوِّن، ولكن قد يكون أحيانًا مفيدًا لإطلاق حدث التركيز أو قياس حجم أو موضع عقدة DOM للمُكوِّن الابن.

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

إن كُنتَ تستخدم إصدار React 16.3 أو أحدث، فنوصي باستخدام تمرير المراجع لأجل هذه الحالات. حيث يُتيح ذلك للمُكوِّنات أن تُعرِّض أي مرجع للمُكوِّن الابن كمرجع خاص بها. ستجد مثالًا مُفصّلًا حول كيفيّة تعريض عقدة DOM للابن إلى المُكوِّن الأب في صفحة تمرير المراجع من هذا التوثيق.

إن كُنتَ تستخدم إصدار React 16.2 أو أقدم، أو احتجتَ مرونة أكبر من تلك التي يُعطيك إيّاها تمرير المراجع، فتستطيع استخدام هذه المقاربة المختلفة لتمرير مرجع كخاصيّة ذات اسم مُختلِف.

ننصح بقدر الإمكان تجنّب تعريض عُقَد DOM، ولكن قد يكون أحيانًا هذا حلًّا جيّدًا للالتفاف حول هذه المشكلة. لاحظ حاجة هذه المقاربة إلى إضافة بعض الشيفرة إلى المُكوِّن الابن. إن لم يكن لديك أي تحكّم بالمُكوِّن الابن فخيارك الأخير هو استخدام findDOMNode()‎، ولكن لا نُوصي بهذا كما أنّها مُتجاوزة في الوضعStrictMode.

ردود نداء المراجع

تدعم React أيضًا طريقة أخرى لتعيين المراجع تُدعى ردود نداء المراجع (callback refs)، والتي تُعطي درجة أكبر من التحكم عند تعيين وإزالة تعيين المراجع.

بدلًا من تمرير الخاصيّة ref المُنشَأة من قبل التابع createRef()‎، مرِّر دالة. تستقبل هذه الدالة نسخة من مُكوِّن React أو عنصر DOM كوسائط لها، والتي يُمكِن تخزينها والوصول إليها من مكان آخر.

يعتمد المثال التالي نمطًا شائعًا: وهو استخدام رد نداء ref لتخزين مرجع إلى عقدة DOM في نسخة من الخاصيّة:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
	  // التركيز على الحقل النصي باستخدام DOM API
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
	// التركيز التلقائي على الحقل النصي عند وصل المُكوِّن (mount)
    this.focusTextInput();
  }

  render() {
	// استخدم رد نداء المرجع لتخزين مرجع إلى عنصر DOM للحقل النصي
	// في نسخة من الحقل (مثل this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="التركيز على الحقل النصي"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

ستستدعي React رد نداء ref عن طريق عنصر DOM عند وصل المُكوِّن (mount)، وتستدعيه مع القيمة null عند فصل المُكوِّن (unmount). نضمن أن تكون قيمة المراجع مُحدَّثة قبل إطلاق توابع المُكوِّن componentDidMount أو componentDidUpdate. تستطيع تمرير ردود نداء المراجع بين المُكوِّنات كما تفعل بين كائنات المراجع التي يُنشِئها React.createRef()‎:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

في المثال السّابق يُمرِّر المُكوِّن Parent رد نداء المرجع من خلال الخاصيّة inputRef إلى المُكوِّن CustomTextInput والذي يُمرِّر نفس الدالة كخاصيّة ref إلى العنصر <input>، وكنتيجة لذلك تُعيَّن this.inputElement في المُكوِّن Parent إلى عقدة DOM المُوافِقة للعنصر <input> في المُكوِّن CustomTextInput.

واجهة برمجة التطبيق API القديمة: مراجع السلاسل النصية

إن تعاملتَ مع React سابقًا فقد تكون متآلفًا مع واجهة برمجة التطبيق (API) القديمة حيث كانت الخاصيّة ref عبارة عن سلسلة نصيّة، مثل textInput، ونصل إلى عقدة DOM عبر this.refs.textInput. لا ننصح بفعل ذلك لأنّ مراجع السلاسل النصيّة لديها بعض المشاكل، وأصبحت مُهمَلة في React، ومن المحتمل أنّها ستُزال في الإصدارات القادمة.

ملاحظة: إن كنتَ تستخدم this.refs.textInput حاليًّا للوصول إلى المراجع، فيُفضَّل أن تستخدم إمّا نمط ردود النداء أو createRef API بدلًا من ذلك.

محاذير استخدام ردود نداء المراجع

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

انظر أيضًا

 مصادر