الفرق بين المراجعتين ل"React/integrating with other libraries"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
سطر 67: سطر 67:
 
}
 
}
  
</syntaxhighlight>لاحظ كيف غلّفنا ‎<nowiki><select>‎ ضمن عنصر ‎<div>‎ إضافي. يكون هذا ضروريًّا لأنّ Chosen ستُلحِق عنصر DOM آخر مباشرةً بعد عقدة العنصر ‎<select>‎ التي مررناها إليها. ولكن على حد علم React فإنّ العنصر ‎<div>‎ يمتلك ابنًا واحدًا فقط. بهذا نضمن عدم تضارب تحديثات React مع عُقَد DOM الإضافية التي تُضيفها Chosen. من الهام عند تعديلك لعقد DOM خارج إطار React أن تضمن ألّا يكون هنالك سبب يجعل React تقترب من عقد DOM هذه.</nowiki>
+
</syntaxhighlight>لاحظ كيف غلّفنا ‎<code><select></code>‎ ضمن عنصر ‎<code><nowiki><div></nowiki></code>‎ إضافي. يكون هذا ضروريًّا لأنّ <code>Chosen</code> ستُلحِق عنصر DOM آخر مباشرةً بعد عقدة العنصر <code>‎<select></code>‎ التي مررناها إليها. ولكن على حد علم React فإنّ العنصر ‎<code><nowiki><div></nowiki></code>‎ يمتلك ابنًا واحدًا فقط. بهذا نضمن عدم تضارب تحديثات React مع عُقَد DOM الإضافية التي تُضيفها <code>Chosen</code>. من الهام عند تعديلك لعقد DOM خارج إطار React أن تضمن ألّا يكون هنالك سبب يجعل React تقترب من عقد DOM هذه.
  
سننفذ بعد ذلك توابع دورة حياة المكونات، نحتاج إلى تهيئة Chosen بالمرجع إلى عقدة ‎<select>‎ في التابع componentDidMount وحذف هذا المرجع في التابع componentWillUnmount:
+
سننفذ بعد ذلك توابع دورة حياة المكونات، نحتاج إلى تهيئة <code>Chosen</code> بالمرجع إلى عقدة ‎<code><select></code>‎ في التابع <code>componentDidMount</code> وحذف هذا المرجع في التابع <code>componentWillUnmount</code>:<syntaxhighlight lang="javascript">
 +
componentDidMount() {
 +
  this.$el = $(this.el);
 +
  this.$el.chosen();
 +
}
 +
 
 +
componentWillUnmount() {
 +
  this.$el.chosen('destroy');
 +
}
 +
 
 +
</syntaxhighlight>جرّب المثال على موقع CodePen.
 +
 
 +
لاحظ أنّ React لا تُخصِّص أي معنى مميز للحقل <code>this.el</code>. فهو يعمل فقط لأنّنا عيّناه من المرجع <code>ref</code> في التابع <code>render()</code>‎:<syntaxhighlight lang="javascript">
 +
‎<select className="Chosen-select" ref={el => this.el = el}>‎
 +
</syntaxhighlight>
 +
 
 +
هذا يكفينا لتصيير المكوّن، ولكننا نريد أيضًا أن تصلنا إشعارات حول القيم التي تغيّرت. لفعل ذلك سنشترك في حدث <code>change</code> في jQuery في العنصر ‎<code><select></code>‎ الذي تديره <code>Chosen</code>.
 +
 
 +
لن نُمرِّر <code>this.props.onChange</code> بشكل مباشر إلى <code>Chosen</code> بسبب إمكانية تغيير خاصيّات المكوّن عبر الزمن وهذا يتضمن معالجات الأحداث. نصرّح بدلًا من ذلك عن التابع <code>handleChange()‎</code> الذي يستدعي <code>this.props.onChange</code> ويجعله يشترك بالحدث <code>change</code> في jQuery:<syntaxhighlight lang="javascript">
 +
componentDidMount() {
 +
  this.$el = $(this.el);
 +
  this.$el.chosen();
 +
 
 +
  this.handleChange = this.handleChange.bind(this);
 +
  this.$el.on('change', this.handleChange);
 +
}
 +
 
 +
componentWillUnmount() {
 +
  this.$el.off('change', this.handleChange);
 +
  this.$el.chosen('destroy');
 +
}
 +
 
 +
handleChange(e) {
 +
  this.props.onChange(e.target.value);
 +
}
 +
 
 +
</syntaxhighlight>جرّب المثال على موقع CodePen.
 +
 
 +
بقي شيء أخير يجب فعله. قد تتغيّر الخاصيّات في React مع مرور الوقت. على سبيل المثال قد يحصل المكوّن ‎<code><Chosen>‎</code> على مكوّنات أبناء مختلفين إن تغيّرت حالة المكوّن الأب له. يعني هذا أهميّة تحديث DOM بشكل يدوي في نقاط التكامل استجابةً لتحديثات الخاصيّات، بما أنّنا لم نعد نترك React تدير DOM لأجلنا.
 +
 
 +
يقترح توثيق Chosen أنّنا نستطيع استخدام الواجهة <code>trigger()</code>‎ للإعلام حول التغييرات في عنصر DOM الأصلي. سنترك React تهتم بتحديث <code>this.props.children</code> بداخل العنصر ‎<code><select></code>‎، ولكننا سنضيف أيضًا تابع دورة الحياة <code>componentDidUpdate()‎</code> والتي تُعلِم <code>Chosen</code> بالتغيرات في قائمة العناصر الأبناء:<syntaxhighlight lang="javascript">
 +
componentDidUpdate(prevProps) {
 +
  if (prevProps.children !== this.props.children) {
 +
    this.$el.trigger("chosen:updated");
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>بهذه الطريقة ستعلم <code>Chosen</code> تحديث عنصر DOM الخاص بها بينما يبقى العنصر الابن لها وهو ‎<code><select></code>‎ مُدارًا من قبل تغيير React.
 +
 
 +
يبدو التنفيذ الكامل للمكوّن <code>Chosen</code> كما يلي:<syntaxhighlight lang="javascript">
 +
class Chosen extends React.Component {
 +
  componentDidMount() {
 +
    this.$el = $(this.el);
 +
    this.$el.chosen();
 +
 
 +
    this.handleChange = this.handleChange.bind(this);
 +
    this.$el.on('change', this.handleChange);
 +
  }
 +
 
 +
  componentDidUpdate(prevProps) {
 +
    if (prevProps.children !== this.props.children) {
 +
      this.$el.trigger("chosen:updated");
 +
    }
 +
  }
 +
 
 +
  componentWillUnmount() {
 +
    this.$el.off('change', this.handleChange);
 +
    this.$el.chosen('destroy');
 +
  }
 +
 
 +
  handleChange(e) {
 +
    this.props.onChange(e.target.value);
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div>
 +
        <select className="Chosen-select" ref={el => this.el = el}>
 +
          {this.props.children}
 +
        </select>
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>جرّب المثال على موقع CodePen.
 +
 
 +
== التكامل مع مكتبات الإظهار الأخرى ==
 +
يُمكِن تضمين React في تطبيقات أخرى بفضل مرونة التابع <code>ReactDOM.render()</code>‎.
 +
 
 +
على الرغم من أنّه من الشائع استخدام React في البداية لتحميل مكوّن React جذري وحيد إلى DOM، يُمكِن استدعاء التابع <code>ReactDOM.render()</code>‎ عدّة مرات للأجزاء المستقلة من واجهة المستخدم والتي قد تكون صغيرة بحجم عنصر الزر <code>button</code> أو كبيرة بحجم تطبيق كامل.
 +
 
 +
في الواقع هذه هي طريقة استخدام React في Facebook. يُتيح لنا ذلك كتابة تطبيقات في React قطعة بقطعة، ومن ثمّ جمعها مع قوالبنا المُولَّدة من قبل الخادم ومع الشيفرات التي من جهة العميل (client-side) الأخرى.
 +
 
 +
استبدال التصيير المعتمد على السلسلة النصية بتصيير React
 +
 
 +
من الأنماط الشائعة في تطبيقات الويب القديمة هي وصف مجموعة من DOM كسلسلة نصيّة وإدخالها في DOM كما يلي: ‎<code>$el.html(htmlString)</code>‎. هذه النقاط ملائمة لتقديم React، فقط أعد كتابة التصيير المعتمد على السلسلة النصيّة إلى مكوّن React.
 +
 
 +
فلنأخذ شيفرة jQuery التالية:<syntaxhighlight lang="javascript">
 +
$('#container').html('<button id="btn">قل مرحبًا</button>');
 +
$('#btn').click(function() {
 +
  alert('مرحبًا!');
 +
});
 +
 
 +
</syntaxhighlight>يُمكِن إعادة كتابتها كمكوّن React كما يلي:<syntaxhighlight lang="javascript">
 +
function Button() {
 +
  return <button id="btn">قل مرحبًا</button>;
 +
}
 +
 
 +
ReactDOM.render(
 +
  <Button />,
 +
  document.getElementById('container'),
 +
  function() {
 +
    $('#btn').click(function() {
 +
      alert('مرحبًا');
 +
    });
 +
  }
 +
);
 +
 
 +
</syntaxhighlight>من هنا بإمكانك إضافة المزيد من منطق React إلى هذا المكوّن والبدء بتبني المزيد من ممارسات React الشائعة. فمثلًا من الأفضل في المكوّنات عدم الاعتماد على المُعرّفات (IDs) بسبب إمكانية تصيير نفس المكوّن مرات عديدة. سنستخدم بدلًا من ذلك نظام أحداث React ونسجل مُعالج حدث الضغط (<code>click</code>) بشكل مباشر على عنصر الزر <code>‎<button></code>‎ في React:<syntaxhighlight lang="javascript">
 +
function Button(props) {
 +
  return <button onClick={props.onClick}>قل مرحبًا</button>;
 +
}
 +
 
 +
function HelloButton() {
 +
  function handleClick() {
 +
    alert('مرحبًا!');
 +
  }
 +
  return <Button onClick={handleClick} />;
 +
}
 +
 
 +
ReactDOM.render(
 +
  <HelloButton />,
 +
  document.getElementById('container')
 +
);
 +
 
 +
</syntaxhighlight>جرّب المثال على موقع CodePen.
 +
 
 +
بإمكانك امتلاك مكوّنات معزولة كما تشاء واستخدام التابع ReactDOM.render()‎ لتصييرها إلى حاويات DOM مختلفة. وبينما تحوّل المزيد من شيفرة تطبيقك إلى React بشكل تدريجي، فستكون قادرًا على جمعها في مكوّنات أكبر ونقل استدعاءات التابع ReactDOM.render()‎ إلى الأعلى في التسلسل الهرمي للمكوّنات.
 +
 
 +
=== تضمين React في واجهة عرض Backbone ===
 +
تستخدم واجهات عرض Backbone بشكل نموذجي سلاسل نصيّة أو دوال منتجة للسلاسل النصيّة لإنشاء المحتوى لعناصر DOM. يُمكِن استبدال هذه العملية بتصيير مكوّن React.
 +
 
 +
سنُنشِئ الآن واجهة عرض Backbone تُدعى ParagraphView والتي ستتجاوز دالة التصيير render()‎ في Backbone لتصيير المكوّن ‎<Paragraph>‎ في React إلى عنصر DOM المُعطى من خلال Backbone (وهو this.el). نستخدم هنا أيضًا التابع ReactDOM.render()‎:

مراجعة 13:46، 24 أغسطس 2018

يمكن استخدام React في أي تطبيق ويب وتضمينها في تطبيقات أخرى، ويمكن أيضًا بجهد قليل تضمين المكتبات الأخرى مع React. سنتحدث في هذه الصفحة عن بعض أشيع الحالات مع التركيز على التكامل مع jQuery و Backbone، ولكن يمكن تطبيق نفس الأفكار لتكامل المكوّنات مع أي شيفرة موجودة حاليًّا.

إضافات التكامل مع DOM

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

لا يعني هذا استحالة أو صعوبة جمع React مع طرق أخرى للتعديل على DOM، ولكن يجب أن تكون على معرفة بما يجري.

أسهل طريقة لتجنب التضاربات هي منع مكوّن React من التحديث. بإمكانك فعل ذلك عن طريق تصيير عناصر لا داع لتحديثها في React، مثل عنصر ‎<div />‎ الفارغ.

كيفيّة حل المشكلة

لتوضيح هذا فلنصنع حاوية لإضافة عامّة في jQuery.

سنضيف مرجع إلى عنصر DOM الجذري. وبداخل التابع componentDidMount سنحصل على مرجع له لكي نستطيع تمريره إلى إضافة jQuery.

ولمنع React من الاقتراب من DOM بعد الوصل، فسنعيد عنصر ‎<div />‎ فارغ من التابع render()‎، لا يمتلك هذا العنصر أي خاصيّات أو أبناء، لذا لا تملك React سببًا لتحديثه، وبذلك نترك إضافة jQuery حرّة لإدارة ذلك الجزء من DOM:

class SomePlugin extends React.Component {
  componentDidMount() {
    this.$el = $(this.el);
    this.$el.somePlugin();
  }

  componentWillUnmount() {
    this.$el.somePlugin('destroy');
  }

  render() {
    return <div ref={el => this.el = el} />;
  }
}

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

التكامل مع إضافة jQuery التي تدعى Chosen

للحصول على مثال أكثر وضوحًا عن هذه المفاهيم فلنكتب تغليف للإضافة Chosen والتي تضيف حقل الإدخال ‎<select>‎.

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

فلننظر أولًا إلى تأثير الإضافة Chosen على DOM.

إن استدعيتها على عقدة DOM لحقل الاختيار ‎<select>‎ فستقرأ الخاصيّات من عقدة DOM الأصليّة، وتخفيها باستخدام التنسيق السطري (inline)، وتُلحِق بعد ذلك عقدة DOM منفصلة مع تمثيلها الخاص بها بعد العنصر ‎<select>‎. بعد ذلك تُطلِق أحداث jQuery لتنبيهنا حول التغييرات.

فلنفترض أنّ هذه هي واجهة برمجة التطبيقات (API) التي نرغب بإضافتها مع المكوّن ‎<Chosen>‎:

function Example() {
  return (
    <Chosen onChange={value => console.log(value)}>
      <option>vanilla</option>
      <option>chocolate</option>
      <option>strawberry</option>
    </Chosen>
  );
}

سننفذها كمكوّن غير مضبوط للسهولة. سنُنشِئ أولًا مكوّنًا فارغًا مع التابع render()‎ حيث نعيد الحقل ‎<select>‎ مُغلَّفًا ضمن عنصر ‎<div>‎:

class Chosen extends React.Component {
  render() {
    return (
      <div>
        <select className="Chosen-select" ref={el => this.el = el}>
          {this.props.children}
        </select>
      </div>
    );
  }
}

لاحظ كيف غلّفنا ‎<select>‎ ضمن عنصر ‎<div>‎ إضافي. يكون هذا ضروريًّا لأنّ Chosen ستُلحِق عنصر DOM آخر مباشرةً بعد عقدة العنصر ‎<select>‎ التي مررناها إليها. ولكن على حد علم React فإنّ العنصر ‎<div>‎ يمتلك ابنًا واحدًا فقط. بهذا نضمن عدم تضارب تحديثات React مع عُقَد DOM الإضافية التي تُضيفها Chosen. من الهام عند تعديلك لعقد DOM خارج إطار React أن تضمن ألّا يكون هنالك سبب يجعل React تقترب من عقد DOM هذه. سننفذ بعد ذلك توابع دورة حياة المكونات، نحتاج إلى تهيئة Chosen بالمرجع إلى عقدة ‎<select>‎ في التابع componentDidMount وحذف هذا المرجع في التابع componentWillUnmount:

componentDidMount() {
  this.$el = $(this.el);
  this.$el.chosen();
}

componentWillUnmount() {
  this.$el.chosen('destroy');
}

جرّب المثال على موقع CodePen. لاحظ أنّ React لا تُخصِّص أي معنى مميز للحقل this.el. فهو يعمل فقط لأنّنا عيّناه من المرجع ref في التابع render()‎:

<select className="Chosen-select" ref={el => this.el = el}>

هذا يكفينا لتصيير المكوّن، ولكننا نريد أيضًا أن تصلنا إشعارات حول القيم التي تغيّرت. لفعل ذلك سنشترك في حدث change في jQuery في العنصر ‎<select>‎ الذي تديره Chosen.

لن نُمرِّر this.props.onChange بشكل مباشر إلى Chosen بسبب إمكانية تغيير خاصيّات المكوّن عبر الزمن وهذا يتضمن معالجات الأحداث. نصرّح بدلًا من ذلك عن التابع handleChange()‎ الذي يستدعي this.props.onChange ويجعله يشترك بالحدث change في jQuery:

componentDidMount() {
  this.$el = $(this.el);
  this.$el.chosen();

  this.handleChange = this.handleChange.bind(this);
  this.$el.on('change', this.handleChange);
}

componentWillUnmount() {
  this.$el.off('change', this.handleChange);
  this.$el.chosen('destroy');
}

handleChange(e) {
  this.props.onChange(e.target.value);
}

جرّب المثال على موقع CodePen.

بقي شيء أخير يجب فعله. قد تتغيّر الخاصيّات في React مع مرور الوقت. على سبيل المثال قد يحصل المكوّن ‎<Chosen>‎ على مكوّنات أبناء مختلفين إن تغيّرت حالة المكوّن الأب له. يعني هذا أهميّة تحديث DOM بشكل يدوي في نقاط التكامل استجابةً لتحديثات الخاصيّات، بما أنّنا لم نعد نترك React تدير DOM لأجلنا.

يقترح توثيق Chosen أنّنا نستطيع استخدام الواجهة trigger()‎ للإعلام حول التغييرات في عنصر DOM الأصلي. سنترك React تهتم بتحديث this.props.children بداخل العنصر ‎<select>‎، ولكننا سنضيف أيضًا تابع دورة الحياة componentDidUpdate()‎ والتي تُعلِم Chosen بالتغيرات في قائمة العناصر الأبناء:

componentDidUpdate(prevProps) {
  if (prevProps.children !== this.props.children) {
    this.$el.trigger("chosen:updated");
  }
}

بهذه الطريقة ستعلم Chosen تحديث عنصر DOM الخاص بها بينما يبقى العنصر الابن لها وهو ‎<select>‎ مُدارًا من قبل تغيير React. يبدو التنفيذ الكامل للمكوّن Chosen كما يلي:

class Chosen extends React.Component {
  componentDidMount() {
    this.$el = $(this.el);
    this.$el.chosen();

    this.handleChange = this.handleChange.bind(this);
    this.$el.on('change', this.handleChange);
  }
  
  componentDidUpdate(prevProps) {
    if (prevProps.children !== this.props.children) {
      this.$el.trigger("chosen:updated");
    }
  }

  componentWillUnmount() {
    this.$el.off('change', this.handleChange);
    this.$el.chosen('destroy');
  }
  
  handleChange(e) {
    this.props.onChange(e.target.value);
  }

  render() {
    return (
      <div>
        <select className="Chosen-select" ref={el => this.el = el}>
          {this.props.children}
        </select>
      </div>
    );
  }
}

جرّب المثال على موقع CodePen.

التكامل مع مكتبات الإظهار الأخرى

يُمكِن تضمين React في تطبيقات أخرى بفضل مرونة التابع ReactDOM.render()‎.

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

في الواقع هذه هي طريقة استخدام React في Facebook. يُتيح لنا ذلك كتابة تطبيقات في React قطعة بقطعة، ومن ثمّ جمعها مع قوالبنا المُولَّدة من قبل الخادم ومع الشيفرات التي من جهة العميل (client-side) الأخرى.

استبدال التصيير المعتمد على السلسلة النصية بتصيير React

من الأنماط الشائعة في تطبيقات الويب القديمة هي وصف مجموعة من DOM كسلسلة نصيّة وإدخالها في DOM كما يلي: ‎$el.html(htmlString)‎. هذه النقاط ملائمة لتقديم React، فقط أعد كتابة التصيير المعتمد على السلسلة النصيّة إلى مكوّن React.

فلنأخذ شيفرة jQuery التالية:

$('#container').html('<button id="btn">قل مرحبًا</button>');
$('#btn').click(function() {
  alert('مرحبًا!');
});

يُمكِن إعادة كتابتها كمكوّن React كما يلي:

function Button() {
  return <button id="btn">قل مرحبًا</button>;
}

ReactDOM.render(
  <Button />,
  document.getElementById('container'),
  function() {
    $('#btn').click(function() {
      alert('مرحبًا');
    });
  }
);

من هنا بإمكانك إضافة المزيد من منطق React إلى هذا المكوّن والبدء بتبني المزيد من ممارسات React الشائعة. فمثلًا من الأفضل في المكوّنات عدم الاعتماد على المُعرّفات (IDs) بسبب إمكانية تصيير نفس المكوّن مرات عديدة. سنستخدم بدلًا من ذلك نظام أحداث React ونسجل مُعالج حدث الضغط (click) بشكل مباشر على عنصر الزر ‎<button>‎ في React:

function Button(props) {
  return <button onClick={props.onClick}>قل مرحبًا</button>;
}

function HelloButton() {
  function handleClick() {
    alert('مرحبًا!');
  }
  return <Button onClick={handleClick} />;
}

ReactDOM.render(
  <HelloButton />,
  document.getElementById('container')
);

جرّب المثال على موقع CodePen.

بإمكانك امتلاك مكوّنات معزولة كما تشاء واستخدام التابع ReactDOM.render()‎ لتصييرها إلى حاويات DOM مختلفة. وبينما تحوّل المزيد من شيفرة تطبيقك إلى React بشكل تدريجي، فستكون قادرًا على جمعها في مكوّنات أكبر ونقل استدعاءات التابع ReactDOM.render()‎ إلى الأعلى في التسلسل الهرمي للمكوّنات.

تضمين React في واجهة عرض Backbone

تستخدم واجهات عرض Backbone بشكل نموذجي سلاسل نصيّة أو دوال منتجة للسلاسل النصيّة لإنشاء المحتوى لعناصر DOM. يُمكِن استبدال هذه العملية بتصيير مكوّن React.

سنُنشِئ الآن واجهة عرض Backbone تُدعى ParagraphView والتي ستتجاوز دالة التصيير render()‎ في Backbone لتصيير المكوّن ‎<Paragraph>‎ في React إلى عنصر DOM المُعطى من خلال Backbone (وهو this.el). نستخدم هنا أيضًا التابع ReactDOM.render()‎: