الفرق بين المراجعتين لصفحة: «React/integrating with other libraries»
Kinan-mawed (نقاش | مساهمات) أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:تكامل React مع المكتبات الأخرى}}</noinclude>' |
تحديث |
||
(9 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:تكامل React مع المكتبات الأخرى}}</noinclude> | <noinclude>{{DISPLAYTITLE:تكامل React مع المكتبات الأخرى في React}}</noinclude> | ||
يمكن استخدام React في أي تطبيق ويب وتضمينها في تطبيقات أخرى، ويمكن أيضًا بجهد قليل تضمين المكتبات الأخرى مع React. سنتحدث في هذه الصفحة ع ن بعض أشيع الحالات مع التركيز على التكامل مع [https://jquery.com/ jQuery] و [http://backbonejs.org/ Backbone]، ولكن يمكن تطبيق نفس الأفكار لتكامل المكوّنات مع أي شيفرة موجودة حاليًّا. | |||
== إضافات التكامل مع DOM == | |||
لا تعلم React بأي تغيير يحصل على DOM من خارج نطاق React. فهي تُحدِّد التحديثات بناءً على تمثيلها الداخلي الخاص، وإن عدّلت أي مكتبة أخرى على نفس عقدة DOM، فستختلط الأمور على React ولن تتمكن من إدراك ما يحصل. | |||
لا يعني هذا استحالة أو صعوبة جمع React مع طرق أخرى للتعديل على DOM، ولكن يجب أن تكون على معرفة بما يجري. | |||
أسهل طريقة لتجنب التضاربات هي منع مكوّن React من التحديث. بإمكانك فعل ذلك عن طريق تصيير عناصر لا داع لتحديثها في React، مثل عنصر <code><nowiki><div /></nowiki></code> الفارغ. | |||
=== كيفيّة حل المشكلة === | |||
لتوضيح هذا فلنصنع حاوية لإضافة عامّة في jQuery. | |||
سنُرفِق [[React/refs and the dom|مرجعًا]] إلى عنصر DOM الجذري. وبداخل التابع <code>componentDidMount</code> سنحصل على مرجع له لكي نستطيع تمريره إلى إضافة jQuery. | |||
ولمنع React من الاقتراب من DOM بعد الوصل، فسنعيد عنصر <code><nowiki><div /></nowiki></code> فارغ من التابع <code>render()</code>، لا يمتلك هذا العنصر أي خاصيّات أو أبناء، لذا لا تملك React سببًا لتحديثه، وبذلك نترك إضافة jQuery حرّة لإدارة ذلك الجزء من DOM:<syntaxhighlight lang="javascript"> | |||
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} />; | |||
} | |||
} | |||
</syntaxhighlight>لاحظ أننا عرّفنا [https://wiki.hsoub.com/React/react_component توابع دورة الحياة] <code>componentDidMount</code> و <code>componentWillUnmount</code>. تربط العديد من إضافات jQuery مستمعات للأحداث إلى DOM لذا من الهام فصلها في التابع <code>componentWillUnmount</code>. إن لم تزودنا الإضافة بطريقة لمسح كل شيء بعد الانتهاء، فيجب عليك إضافة طريقتك الخاصة مع تذكر إزالة أي مستمع للأحداث سجلته الإضافة لمنع أي تسريب في الذاكرة. | |||
=== التكامل مع إضافة jQuery التي تدعى <code>Chosen</code> === | |||
للحصول على مثال أكثر وضوحًا عن هذه المفاهيم فلنكتب تغليفًا للإضافة <code>[https://harvesthq.github.io/chosen/ Chosen]</code> والتي تضيف حقل الإدخال <code><select></code>. | |||
'''ملاحظة:''' لا تعني إمكانيّة فعل ذلك أنّ هذه هي الطريقة الأفضل من أجل تطبيقات React. نشجعك دومًا على استخدام المكوّنات قدر الإمكان، حيث من الأسهل إعادة استخدامها في تطبيقات React وتُعطينا تحكّمًا أكبر في السلوك والمظهر. | |||
فلننظر أولًا إلى تأثير الإضافة <code>Chosen</code> على DOM. | |||
إن استدعيتها على عقدة DOM لحقل الاختيار <select> فستقرأ الخاصيّات من عقدة DOM الأصليّة، وتخفيها باستخدام التنسيق السطري (inline)، وتُلحِق بعد ذلك عقدة DOM منفصلة مع تمثيلها الخاص بها بعد العنصر <code><select></code>. بعد ذلك تُطلِق أحداث jQuery لتنبيهنا حول التغييرات. | |||
فلنفترض أنّ هذه هي واجهة برمجة التطبيقات (API) التي نرغب بإضافتها مع المكوّن <code><Chosen></code>:<syntaxhighlight lang="javascript"> | |||
function Example() { | |||
return ( | |||
<Chosen onChange={value => console.log(value)}> | |||
<option>vanilla</option> | |||
<option>chocolate</option> | |||
<option>strawberry</option> | |||
</Chosen> | |||
); | |||
} | |||
</syntaxhighlight>سننفذها [[React/uncontrolled components|كمكوّن غير مضبوط]] للسهولة. | |||
سنُنشِئ أولًا مكوّنًا فارغًا مع التابع <code>render()</code> حيث نعيد الحقل <code><select></code> مُغلَّفًا ضمن عنصر <code><nowiki><div></nowiki></code>:<syntaxhighlight lang="javascript"> | |||
class Chosen extends React.Component { | |||
render() { | |||
return ( | |||
<div> | |||
<select className="Chosen-select" ref={el => this.el = el}> | |||
{this.props.children} | |||
</select> | |||
</div> | |||
); | |||
} | |||
} | |||
</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 هذه. | |||
سننفذ بعد ذلك توابع دورة حياة المكونات، نحتاج إلى تهيئة <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>[http://codepen.io/gaearon/pen/qmqeQx?editors=0010 جرّب المثال على موقع 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>[http://codepen.io/gaearon/pen/bWgbeE?editors=0010 جرّب المثال على موقع 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>[http://codepen.io/gaearon/pen/xdgKOz?editors=0010 جرّب المثال على موقع CodePen]. | |||
== التكامل مع مكتبات الإظهار الأخرى == | |||
يُمكِن تضمين React في تطبيقات أخرى بفضل مرونة التابع <code>[https://reactjs.org/docs/react-dom.html#render 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) بسبب إمكانية تصيير نفس المكوّن مرات عديدة. سنستخدم بدلًا من ذلك [https://wiki.hsoub.com/React/handling_events نظام أحداث 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>[http://codepen.io/gaearon/pen/RVKbvW?editors=1010 جرّب المثال على موقع CodePen]. | |||
بإمكانك امتلاك مكوّنات معزولة كما تشاء واستخدام التابع <code>ReactDOM.render()</code> لتصييرها إلى حاويات DOM مختلفة. وبينما تحوّل المزيد من شيفرة تطبيقك إلى React بشكل تدريجي، فستكون قادرًا على جمعها في مكوّنات أكبر ونقل استدعاءات التابع <code>ReactDOM.render()</code> إلى الأعلى في التسلسل الهرمي للمكوّنات. | |||
=== تضمين React في واجهة عرض Backbone === | |||
تستخدم واجهات عرض [http://backbonejs.org/ Backbone] بشكل نموذجي سلاسل نصيّة أو دوال منتجة للسلاسل النصيّة لإنشاء المحتوى لعناصر DOM. يُمكِن استبدال هذه العملية بتصيير مكوّن React. | |||
سنُنشِئ الآن واجهة عرض Backbone تُدعى <code>ParagraphView</code> والتي ستتجاوز دالة التصيير <code>render()</code> في Backbone لتصيير المكوّن <code><Paragraph></code> في React إلى عنصر DOM المُعطى من خلال Backbone (وهو <code>this.el</code>). نستخدم هنا أيضًا التابع <code>[https://reactjs.org/docs/react-dom.html#render ReactDOM.render()]</code>:<syntaxhighlight lang="javascript"> | |||
function Paragraph(props) { | |||
return <p>{props.text}</p>; | |||
} | |||
const ParagraphView = Backbone.View.extend({ | |||
render() { | |||
const text = this.model.get('text'); | |||
ReactDOM.render(<Paragraph text={text} />, this.el); | |||
return this; | |||
}, | |||
remove() { | |||
ReactDOM.unmountComponentAtNode(this.el); | |||
Backbone.View.prototype.remove.call(this); | |||
} | |||
}); | |||
</syntaxhighlight>[http://codepen.io/gaearon/pen/gWgOYL?editors=0010 جرّب المثال على موقع CodePen]. | |||
من الهام أن نستدعي أيضًا التابع <code>ReactDOM.unmountComponentAtNode()</code> في تابع الإزالة <code>remove</code> بحيث تلغي React تسجيل معالجات الأحداث والموارد الأخرى المرتبطة بشجرة المكوّن عند فصلها. | |||
عند إزالة المكوّن من شجرة React، يُنفَّذ تابع المسح بشكل تلقائي، ولكن بما أننا نزيل كامل الشجرة يدويًّا فيجب أن نستدعي هذا التابع. | |||
== التكامل مع طبقات النموذج (Model Layers) == | |||
من المفضّل استخدام تدفق البيانات أحادي الاتجاه مثل [https://wiki.hsoub.com/React/lifting_state_up React state]، أو [http://facebook.github.io/flux/ Flux]، أو [http://redux.js.org/ Redux]، ولكن يُمكِن لمكوّنات React استخدام طبقة النموذج (model layer) من أطر العمل والمكتبات الأخرى. | |||
=== استخدام نماذج Backbone في مكوّنات React === | |||
أبسط طريقة لاستهلاك نماذج [http://backbonejs.org/ Backbone] والمجموعات من قبل مكوّنات React هي الاستماع إلى أحداث التغيير المختلفة وإجبار التحديثات يدويًّا. | |||
تستمع المكوّنات المسؤولة عن تصيير النماذج إلى الحدث change، بينما تستمع المكوّنات المسؤولة عن تصيير المجموعات إلى الحدثين <code>add</code> و <code>remove</code>. استخدم في كلتا الحالتين التابع <code>[https://reactjs.org/docs/react-component.html#forceupdate this.forceUpdate()]</code> لإعادة تصيير المكوّن مع البيانات الجديدة. | |||
في المثال التالي يُصيِّر المكوّن <code>List</code> مجموعة Backbone باستخدام المكوّن <code>Item</code> لتصيير عناصر مفردة:<syntaxhighlight lang="javascript"> | |||
class Item extends React.Component { | |||
constructor(props) { | |||
super(props); | |||
this.handleChange = this.handleChange.bind(this); | |||
} | |||
handleChange() { | |||
this.forceUpdate(); | |||
} | |||
componentDidMount() { | |||
this.props.model.on('change', this.handleChange); | |||
} | |||
componentWillUnmount() { | |||
this.props.model.off('change', this.handleChange); | |||
} | |||
render() { | |||
return <li>{this.props.model.get('text')}</li>; | |||
} | |||
} | |||
class List extends React.Component { | |||
constructor(props) { | |||
super(props); | |||
this.handleChange = this.handleChange.bind(this); | |||
} | |||
handleChange() { | |||
this.forceUpdate(); | |||
} | |||
componentDidMount() { | |||
this.props.collection.on('add', 'remove', this.handleChange); | |||
} | |||
componentWillUnmount() { | |||
this.props.collection.off('add', 'remove', this.handleChange); | |||
} | |||
render() { | |||
return ( | |||
<ul> | |||
{this.props.collection.map(model => ( | |||
<Item key={model.cid} model={model} /> | |||
))} | |||
</ul> | |||
); | |||
} | |||
} | |||
</syntaxhighlight>[http://codepen.io/gaearon/pen/GmrREm?editors=0010 جرّب المثال على موقع CodePen]. | |||
=== استخراج البيانات من نماذج Backbone === | |||
يتطلّب الأسلوب السابق من مكوّنات React أن تكون على دراية بنماذج ومجموعات Backbone. إن قررت لاحقًا النقل إلى حل آخر لإدارة البيانات فربّما سترغب بالتركيز المعرفة على Backbone في معظم أجزاء الشيفرة قدر الإمكان. | |||
أحد الحلول لهذه المشكلة هي استخراج خاصيّة النموذج كبيانات مجرّدة عندما تتغيّر، والاحتفاظ بهذا المنطق في مكان واحد. المثال التالي الذي سنتحدّث عنه هو عبارة عن مكوّن ذو ترتيب أعلى يستخرج كل الخاصيّات من نموذج Backbone إلى حالة، مع تمرير البيانات إلى المكوّن المُغلّف. | |||
بهذه الطريقة تحتاج فقط المكوّنات ذات الترتيب الأعلى إلى معرفة تفاصيل نموذج Backbone، أمّا باقي المكوّنات في التطبيق فتستطيع أن تبقى دون دراية بتفاصيل Backbone | |||
في المثال التالي سنُنشِئ نسخة من خاصيّات النموذج لتشكيل الحالة المبدئية. سنشترك في الحدث <code>change</code> (ونزيل الاشتراك عند الفصل)، وعندما يحصل هذا الحدث فسنُحدِّث الحالة مع خاصيّات النموذج الحاليّة. يجب أن نحرص أخيرًا إنّه إن تغيّرت الخاصيّة <code>model</code> نفسها، فلا يجب أن ننسى إزالة الاشتراك من النموذج القديم والاشتراك بالنموذج الجديد. | |||
لاحظ أنّ هذا المثال لا يقصد التعامل مع Backbone بشكل متقدم، ولكنّه يجب أن يعطيك فكرة عن كيفيّة التعامل معها بشكل عام:<syntaxhighlight lang="javascript"> | |||
function connectToBackboneModel(WrappedComponent) { | |||
return class BackboneComponent extends React.Component { | |||
constructor(props) { | |||
super(props); | |||
this.state = Object.assign({}, props.model.attributes); | |||
this.handleChange = this.handleChange.bind(this); | |||
} | |||
componentDidMount() { | |||
this.props.model.on('change', this.handleChange); | |||
} | |||
componentWillReceiveProps(nextProps) { | |||
this.setState(Object.assign({}, nextProps.model.attributes)); | |||
if (nextProps.model !== this.props.model) { | |||
this.props.model.off('change', this.handleChange); | |||
nextProps.model.on('change', this.handleChange); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.props.model.off('change', this.handleChange); | |||
} | |||
handleChange(model) { | |||
this.setState(model.changedAttributes()); | |||
} | |||
render() { | |||
const propsExceptModel = Object.assign({}, this.props); | |||
delete propsExceptModel.model; | |||
return <WrappedComponent {...propsExceptModel} {...this.state} />; | |||
} | |||
} | |||
} | |||
</syntaxhighlight>لتوضيح كيفية استخدامها سنصل المكوّن <code>NameInput</code> في React إلى نموذج Backbone ونُحدِّث خاصيّته <code>firstName</code> في كل مرة يتغير فيها حقل الإدخال:<syntaxhighlight lang="javascript"> | |||
function NameInput(props) { | |||
return ( | |||
<p> | |||
<input value={props.firstName} onChange={props.handleChange} /> | |||
<br /> | |||
اسمي هو {props.firstName}. | |||
</p> | |||
); | |||
} | |||
const BackboneNameInput = connectToBackboneModel(NameInput); | |||
function Example(props) { | |||
function handleChange(e) { | |||
props.model.set('firstName', e.target.value); | |||
} | |||
return ( | |||
<BackboneNameInput | |||
model={props.model} | |||
handleChange={handleChange} | |||
/> | |||
); | |||
} | |||
const model = new Backbone.Model({ firstName: 'Frodo' }); | |||
ReactDOM.render( | |||
<Example model={model} />, | |||
document.getElementById('root') | |||
); | |||
</syntaxhighlight>[http://codepen.io/gaearon/pen/PmWwwa?editors=0010 جرّب المثال على موقع CodePen]. | |||
هذه الطريقة ليست محدودة بمكتبة Backbone. فبإمكانك استخدام React مع أي مكتبة نماذج عن طريق المشاركة في تغييراتها بتوابع دورة حياة المكوّن، وبشكل اختياري نسخ البيانات إلى حالة React المحليّة. | |||
== انظر أيضًا == | |||
* [[React/jsx in depth|شرح JSX بالتفصيل]] | |||
* [[React/static type checking|التحقق من الأنواع الثابتة]] | |||
* [[React/typechecking with proptypes|التحقق من الأنواع باستخدام PropTypes]] | |||
* [[React/refs and the dom|استخدام المراجع مع DOM]] | |||
* [[React/uncontrolled components|المكونات غير المضبوطة]] | |||
* [[React/optimizing performance|تحسين الأداء]] | |||
* [[React/react without es6|React بدون ES6]] | |||
* [[React/react without jsx|React بدون JSX]] | |||
* [[React/reconciliation|المطابقة (Reconciliation)]] | |||
* [[React/context|استخدام السياق (Context) في React]] | |||
* [[React/fragments|استخدام الأجزاء (Fragments) في React]] | |||
* [[React/portals|المداخل (Portals) في React]] | |||
* [[React/error boundaries|حدود الأخطاء]] | |||
* [[React/web components|مكونات الويب]] | |||
* [[React/higher order components|المكونات ذات الترتيب الأعلى]] | |||
* [[React/forwarding refs|تمرير المراجع]] | |||
* [[React/render props|خاصيات التصيير]] | |||
* [[React/accessibility|سهولة الوصول]] | |||
* [[React/code splitting|تقسيم الشيفرة]] | |||
* [[React/strict mode|الوضع الصارم (Strict Mode)]] | |||
== مصادر== | |||
*[https://reactjs.org/docs/integrating-with-other-libraries.html صفحة تكامل React مع المكتبات الأخرى في توثيق React الرسمي]. | |||
[[تصنيف:React]] | |||
[[تصنيف:React Advanced Guides]] |
المراجعة الحالية بتاريخ 19:35، 3 نوفمبر 2020
يمكن استخدام 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);
}
بقي شيء أخير يجب فعله. قد تتغيّر الخاصيّات في 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>
);
}
}
التكامل مع مكتبات الإظهار الأخرى
يُمكِن تضمين 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')
);
بإمكانك امتلاك مكوّنات معزولة كما تشاء واستخدام التابع ReactDOM.render()
لتصييرها إلى حاويات DOM مختلفة. وبينما تحوّل المزيد من شيفرة تطبيقك إلى React بشكل تدريجي، فستكون قادرًا على جمعها في مكوّنات أكبر ونقل استدعاءات التابع ReactDOM.render()
إلى الأعلى في التسلسل الهرمي للمكوّنات.
تضمين React في واجهة عرض Backbone
تستخدم واجهات عرض Backbone بشكل نموذجي سلاسل نصيّة أو دوال منتجة للسلاسل النصيّة لإنشاء المحتوى لعناصر DOM. يُمكِن استبدال هذه العملية بتصيير مكوّن React.
سنُنشِئ الآن واجهة عرض Backbone تُدعى ParagraphView
والتي ستتجاوز دالة التصيير render()
في Backbone لتصيير المكوّن <Paragraph>
في React إلى عنصر DOM المُعطى من خلال Backbone (وهو this.el
). نستخدم هنا أيضًا التابع ReactDOM.render()
:
function Paragraph(props) {
return <p>{props.text}</p>;
}
const ParagraphView = Backbone.View.extend({
render() {
const text = this.model.get('text');
ReactDOM.render(<Paragraph text={text} />, this.el);
return this;
},
remove() {
ReactDOM.unmountComponentAtNode(this.el);
Backbone.View.prototype.remove.call(this);
}
});
من الهام أن نستدعي أيضًا التابع ReactDOM.unmountComponentAtNode()
في تابع الإزالة remove
بحيث تلغي React تسجيل معالجات الأحداث والموارد الأخرى المرتبطة بشجرة المكوّن عند فصلها.
عند إزالة المكوّن من شجرة React، يُنفَّذ تابع المسح بشكل تلقائي، ولكن بما أننا نزيل كامل الشجرة يدويًّا فيجب أن نستدعي هذا التابع.
التكامل مع طبقات النموذج (Model Layers)
من المفضّل استخدام تدفق البيانات أحادي الاتجاه مثل React state، أو Flux، أو Redux، ولكن يُمكِن لمكوّنات React استخدام طبقة النموذج (model layer) من أطر العمل والمكتبات الأخرى.
استخدام نماذج Backbone في مكوّنات React
أبسط طريقة لاستهلاك نماذج Backbone والمجموعات من قبل مكوّنات React هي الاستماع إلى أحداث التغيير المختلفة وإجبار التحديثات يدويًّا.
تستمع المكوّنات المسؤولة عن تصيير النماذج إلى الحدث change، بينما تستمع المكوّنات المسؤولة عن تصيير المجموعات إلى الحدثين add
و remove
. استخدم في كلتا الحالتين التابع this.forceUpdate()
لإعادة تصيير المكوّن مع البيانات الجديدة.
في المثال التالي يُصيِّر المكوّن List
مجموعة Backbone باستخدام المكوّن Item
لتصيير عناصر مفردة:
class Item extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.forceUpdate();
}
componentDidMount() {
this.props.model.on('change', this.handleChange);
}
componentWillUnmount() {
this.props.model.off('change', this.handleChange);
}
render() {
return <li>{this.props.model.get('text')}</li>;
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.forceUpdate();
}
componentDidMount() {
this.props.collection.on('add', 'remove', this.handleChange);
}
componentWillUnmount() {
this.props.collection.off('add', 'remove', this.handleChange);
}
render() {
return (
<ul>
{this.props.collection.map(model => (
<Item key={model.cid} model={model} />
))}
</ul>
);
}
}
استخراج البيانات من نماذج Backbone
يتطلّب الأسلوب السابق من مكوّنات React أن تكون على دراية بنماذج ومجموعات Backbone. إن قررت لاحقًا النقل إلى حل آخر لإدارة البيانات فربّما سترغب بالتركيز المعرفة على Backbone في معظم أجزاء الشيفرة قدر الإمكان.
أحد الحلول لهذه المشكلة هي استخراج خاصيّة النموذج كبيانات مجرّدة عندما تتغيّر، والاحتفاظ بهذا المنطق في مكان واحد. المثال التالي الذي سنتحدّث عنه هو عبارة عن مكوّن ذو ترتيب أعلى يستخرج كل الخاصيّات من نموذج Backbone إلى حالة، مع تمرير البيانات إلى المكوّن المُغلّف.
بهذه الطريقة تحتاج فقط المكوّنات ذات الترتيب الأعلى إلى معرفة تفاصيل نموذج Backbone، أمّا باقي المكوّنات في التطبيق فتستطيع أن تبقى دون دراية بتفاصيل Backbone
في المثال التالي سنُنشِئ نسخة من خاصيّات النموذج لتشكيل الحالة المبدئية. سنشترك في الحدث change
(ونزيل الاشتراك عند الفصل)، وعندما يحصل هذا الحدث فسنُحدِّث الحالة مع خاصيّات النموذج الحاليّة. يجب أن نحرص أخيرًا إنّه إن تغيّرت الخاصيّة model
نفسها، فلا يجب أن ننسى إزالة الاشتراك من النموذج القديم والاشتراك بالنموذج الجديد.
لاحظ أنّ هذا المثال لا يقصد التعامل مع Backbone بشكل متقدم، ولكنّه يجب أن يعطيك فكرة عن كيفيّة التعامل معها بشكل عام:
function connectToBackboneModel(WrappedComponent) {
return class BackboneComponent extends React.Component {
constructor(props) {
super(props);
this.state = Object.assign({}, props.model.attributes);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.model.on('change', this.handleChange);
}
componentWillReceiveProps(nextProps) {
this.setState(Object.assign({}, nextProps.model.attributes));
if (nextProps.model !== this.props.model) {
this.props.model.off('change', this.handleChange);
nextProps.model.on('change', this.handleChange);
}
}
componentWillUnmount() {
this.props.model.off('change', this.handleChange);
}
handleChange(model) {
this.setState(model.changedAttributes());
}
render() {
const propsExceptModel = Object.assign({}, this.props);
delete propsExceptModel.model;
return <WrappedComponent {...propsExceptModel} {...this.state} />;
}
}
}
لتوضيح كيفية استخدامها سنصل المكوّن NameInput
في React إلى نموذج Backbone ونُحدِّث خاصيّته firstName
في كل مرة يتغير فيها حقل الإدخال:
function NameInput(props) {
return (
<p>
<input value={props.firstName} onChange={props.handleChange} />
<br />
اسمي هو {props.firstName}.
</p>
);
}
const BackboneNameInput = connectToBackboneModel(NameInput);
function Example(props) {
function handleChange(e) {
props.model.set('firstName', e.target.value);
}
return (
<BackboneNameInput
model={props.model}
handleChange={handleChange}
/>
);
}
const model = new Backbone.Model({ firstName: 'Frodo' });
ReactDOM.render(
<Example model={model} />,
document.getElementById('root')
);
هذه الطريقة ليست محدودة بمكتبة Backbone. فبإمكانك استخدام React مع أي مكتبة نماذج عن طريق المشاركة في تغييراتها بتوابع دورة حياة المكوّن، وبشكل اختياري نسخ البيانات إلى حالة React المحليّة.
انظر أيضًا
- شرح JSX بالتفصيل
- التحقق من الأنواع الثابتة
- التحقق من الأنواع باستخدام PropTypes
- استخدام المراجع مع DOM
- المكونات غير المضبوطة
- تحسين الأداء
- React بدون ES6
- React بدون JSX
- المطابقة (Reconciliation)
- استخدام السياق (Context) في React
- استخدام الأجزاء (Fragments) في React
- المداخل (Portals) في React
- حدود الأخطاء
- مكونات الويب
- المكونات ذات الترتيب الأعلى
- تمرير المراجع
- خاصيات التصيير
- سهولة الوصول
- تقسيم الشيفرة
- الوضع الصارم (Strict Mode)