الفرق بين المراجعتين لصفحة: «ReactNative/communication ios»
لا ملخص تعديل |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
(10 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:التواصل بين المكون الأصيل | <noinclude>{{DISPLAYTITLE:التواصل بين المكون الأصيل وبين React Native في نظام iOS}}</noinclude> | ||
يمكنك تعلّم كيفية تضمين React Native في مكون أصيل والعكس من صفحة [[ReactNative/integration with existing apps|الدمج مع تطبيقات قائمة]] وصفحة [[ReactNative/native components ios|مكونات واجهة المستخدم الأصيلة]]. عندما نمزج المكونات الأصيلة ومكونات React Native، سنجد حتما الحاجة للتواصل بين هذين العالمين. ذكرنا بعض الطرق لتحقيق ذلك في أدلّة أخرى مسبقا. يلخص هذا المقال التقنيات المتاحة. | |||
يمكنك تعلّم كيفية تضمين React Native في مكون أصيل والعكس من صفحة [[ReactNative/integration with existing apps|الدمج مع تطبيقات قائمة]] وصفحة [[ReactNative/native components ios|مكونات واجهة المستخدم الأصيلة]]. عندما نمزج المكونات الأصيلة ومكونات React Native، سنجد حتما الحاجة للتواصل بين | |||
==مقدمة== | ==مقدمة== | ||
React Native مستوحًى من | React Native مستوحًى من [[React]]، وبالتالي فإن الفكرة الأساسية لتدفق المعلومات (information flow) متشابهة. التدفق في React أحادي الاتجاه. نحافظ على تسلسل هرمي للمكونات، حيث يعتمد كل مكون فقط على مكوِّنه الأب وحالته الداخلية. وذلك باستعمال الخاصيات: تُمرَّر البيانات من أحد المكونات الآباء إلى مكوناته الأبناء من أعلى إلى أسفل. إذا كان أحد مكونات الأسلاف (ancestor component) يعتمد على حالة سليله (descendant)، فيجب أن تُمرَّر دالة رد نداء (callback) ليستخدمها المكون السليل لتحديث المكون السلف. | ||
ينطبق نفس المفهوم على React Native. طالما نقوم ببناء تطبيقنا حصريا داخل إطار العمل، يمكننا التحكم بتطبيقنا بالخاصيات ودوال رد النداء. ولكن، عندما نخلط بين مكونات React Native والمكونات الأصيلة، فإننا نحتاج إلى بعض الآليات الخاصة متعددة اللغات (cross-language) التي ستسمح لنا بتمرير المعلومات بينها. | ينطبق نفس المفهوم على React Native. طالما نقوم ببناء تطبيقنا حصريا داخل إطار العمل، يمكننا التحكم بتطبيقنا بالخاصيات ودوال رد النداء. ولكن، عندما نخلط بين مكونات React Native والمكونات الأصيلة، فإننا نحتاج إلى بعض الآليات الخاصة متعددة اللغات (cross-language) التي ستسمح لنا بتمرير المعلومات بينها. | ||
==الخاصيات== | ==الخاصيات== | ||
الخاصيات هي أبسط طريقة للتواصل عبر المكونات. لذلك نحتاج إلى طريقة لتمرير الخاصيات من كل من المكون الأصيل إلى React Native، | الخاصيات هي أبسط طريقة للتواصل عبر المكونات. لذلك نحتاج إلى طريقة لتمرير الخاصيات من كل من المكون الأصيل إلى React Native، ومن React Native إلى المكون الأصيل. | ||
===تمرير الخاصيات من المكون الأصيل إلى React Native=== | ===تمرير الخاصيات من المكون الأصيل إلى React Native=== | ||
لتضمين | لتضمين واجهة React Native في مكون أصيل، نستخدم <code>RCTRootView</code>، وهو صنفٌ من النوع <code>UIView</code> يحمل تطبيق React Native. كما يوفر واجهة بين الجانب الأصيل والتطبيق المستضاف. | ||
يحتوي <code>RCTRootView</code> على مُهيئ يسمح لك بتمرير خاصيات | يحتوي <code>RCTRootView</code> على مُهيئ يسمح لك بتمرير خاصيات اعتباطيّة إلى تطبيق React Native. يجب أن يكون المعامل <code>initialProperties</code> نسخةً (instance) من <code>NSDictionary</code>. يُحوَّل القاموس داخليًا إلى كائن JSON الذي يمكن لمكون JavaScript ذي مستوى عال (top-level) الرجوع إليه. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="objective-c"> | ||
NSArray *imageList = @[@"http://foo.com/bar1.png", | NSArray *imageList = @[@"http://foo.com/bar1.png", | ||
@"http://foo.com/bar2.png"]; | @"http://foo.com/bar2.png"]; | ||
سطر 28: | سطر 27: | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
import React from 'react'; | import React from 'react'; | ||
import { | import { View, Image } from 'react-native'; | ||
class ImageBrowserApp extends React.Component { | export default class ImageBrowserApp extends React.Component { | ||
renderImage(imgURI) { | renderImage(imgURI) { | ||
return <Image source={{uri: imgURI}} />; | return <Image source={{ uri: imgURI }} />; | ||
} | } | ||
render() { | render() { | ||
سطر 38: | سطر 37: | ||
} | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
يوفر <code>RCTRootView</code> كذلك الخاصية <code>appProperties</code> للقراءة والكتابة (read-write property). بعد تعيين <code>appProperties</code>، يُعاد تصيير تطبيق React Native بخاصيات جديدة. يُنفَّذ التحديث فقط عندما تختلف الخاصيات المحدثة الجديدة عن الخاصيات السابقة. | يوفر <code>RCTRootView</code> كذلك الخاصية <code>appProperties</code> للقراءة والكتابة (read-write property). بعد تعيين <code>appProperties</code>، يُعاد تصيير تطبيق React Native بخاصيات جديدة. يُنفَّذ التحديث فقط عندما تختلف الخاصيات المحدثة الجديدة عن الخاصيات السابقة. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="objective-c"> | ||
NSArray *imageList = @[@"http://foo.com/bar3.png", | NSArray *imageList = @[@"http://foo.com/bar3.png", | ||
@"http://foo.com/bar4.png"]; | @"http://foo.com/bar4.png"]; | ||
سطر 51: | سطر 48: | ||
لا بأس في تحديث الخاصيات في أي وقت. ومع ذلك، يجب إجراء التحديثات على السلسلة الرئيسية. يمكنك استخدام دالة getter على أي سلسلة. | لا بأس في تحديث الخاصيات في أي وقت. ومع ذلك، يجب إجراء التحديثات على السلسلة الرئيسية. يمكنك استخدام دالة getter على أي سلسلة. | ||
'''ملاحظة''': هناك حاليًا مشكلة معروفة عند تعيين <code>appProperties</code> أثناء بدء تشغيل الجسر، يمكن | '''ملاحظة''': هناك حاليًا مشكلة معروفة عند تعيين <code>appProperties</code> أثناء بدء تشغيل الجسر، إذ يمكن فقدان التغيير. انظر [https://github.com/facebook/react-native/issues/20115 هذه الصفحة] لمزيد من المعلومات. | ||
لا توجد وسيلة لتحديث بعض الخاصيات فقط في كل مرة. نقترح بناءه داخل غلاف (wrapper) خاص بك بدلاً من ذلك. | لا توجد وسيلة لتحديث بعض الخاصيات فقط في كل مرة. نقترح بناءه داخل غلاف (wrapper) خاص بك بدلاً من ذلك. | ||
سطر 57: | سطر 54: | ||
'''ملاحظة''': حاليًا، لن تُستدعَى الدالة <code>componentWillUpdateProps</code> في JavaScript لمكون React Native ذي المستوى الأعلى بعد تحديث خاصية ما. ومع ذلك، يمكنك الوصول إلى الخاصيات الجديدة داخل الدالة <code>componentDidMount</code>. | '''ملاحظة''': حاليًا، لن تُستدعَى الدالة <code>componentWillUpdateProps</code> في JavaScript لمكون React Native ذي المستوى الأعلى بعد تحديث خاصية ما. ومع ذلك، يمكنك الوصول إلى الخاصيات الجديدة داخل الدالة <code>componentDidMount</code>. | ||
===تمرير الخاصيات | ===تمرير الخاصيات من React Native إلى المكون الأصيل=== | ||
تمّت تغطية مشكلة توفير الوصول لخاصيات المكونات الأصيلة بالتفصيل في [ | تمّت تغطية مشكلة توفير الوصول لخاصيات المكونات الأصيلة بالتفصيل في [[ReactNative/native components ios#.D8.A7.D9.84.D8.AE.D8.A7.D8.B5.D9.8A.D8.A7.D8.AA|هذه الصفحة]]. باختصار، صدِّر (export) الخاصيات باستخدام ماكرو <code>RCT_CUSTOM_VIEW_PROPERTY</code> في مكونك الأصيل المخصص، ثم استخدمها في React Native كما لو كان المكون مُجرَّد مكونِ React Native عادي. | ||
===حدود الخاصيات=== | ===حدود الخاصيات=== | ||
العيب الرئيسي للخاصيات متعددة اللغات هو أنها لا تدعم دوال رد النداء، والتي تسمح لنا بمعالجة ارتباطات البيانات من أسفل إلى أعلى. تخيل أن لديك | العيب الرئيسي للخاصيات متعددة اللغات هو أنها لا تدعم دوال رد النداء، والتي تسمح لنا بمعالجة ارتباطات البيانات من أسفل إلى أعلى. تخيل أن لديك واجهة React Native صغيرة تريد إزالتها من واجهة المكون الأصيل الأب كنتيجة لإجراءٍ في JavaScript. لا توجد طريقة للقيام بذلك باستخدام الخاصيات، لأن المعلومات ستحتاج إلى الانتقال من الأسفل إلى الأعلى. | ||
رغم أن لدينا نوع من أنواع دوال رد النداء متعددة اللغات ([ | رغم أن لدينا نوع من أنواع دوال رد النداء متعددة اللغات ([[ReactNative/native modules ios#.D8.AF.D9.88.D8.A7.D9.84 .D8.B1.D8.AF .D8.A7.D9.84.D9.86.D8.AF.D8.A7.D8.A1 Callbacks|الموصوفة هنا]])، إلا أن دوال رد النداء هذه ليست دائمًا ما نحتاجه. المشكلة الرئيسية هي أنها غير مُصمَّمةٍ لتُمرَّر كخاصيات. بدلاً من ذلك، تسمح لنا هذه الآلية بتشغيل إجراء أصيل من JavaScript، والتعامل مع نتيجة هذا الإجراء في JavaScript. | ||
==طرق أخرى للتفاعلات متعددة اللغات (الأحداث والوحدات الأصيلة)== | ==طرق أخرى للتفاعلات متعددة اللغات (الأحداث والوحدات الأصيلة)== | ||
كما هو مذكور في الفصل السابق، يأتي استخدام الخاصيات مع بعض القيود. أحيانًا لا تكفي الخاصيات للتحكم بمنطق تطبيقنا، ونحتاج حلا يمنح مزيدًا من المرونة. يغطي هذا الفصل تقنيات | كما هو مذكور في الفصل السابق، يأتي استخدام الخاصيات مع بعض القيود. أحيانًا لا تكفي الخاصيات للتحكم بمنطق تطبيقنا، ونحتاج حلا يمنح مزيدًا من المرونة. يغطي هذا الفصل تقنيات تواصل أخرى متاحة في React Native. يمكن استخدامها للتواصل الداخلي (بين JavaScript والطبقات الأصيلة في React Native) وكذلك للتواصل الخارجي (بين React Native والجزء "الأصيل الخالص" من تطبيقك). | ||
يمكّنك React Native من إجراء استدعاءات الدوال متعددة اللغات. يمكنك تنفيذ شيفرة أصيلة مخصصة من JavaScript والعكس. لسوء الحظ، بناءً على الجانب الذي نعمل عليه، نحقق نفس الهدف بطرق مختلفة. بالنسبة للجهة الأصيلة - نستخدم آلية الأحداث لجدولة تنفيذ دالة معالِجةٍ (handler function) في JavaScript، بينما بالنسبة لإطار React Native، فإننا نستدعي مباشرةً التوابع المصدَّرة من الوحدات الأصيلة. | يمكّنك React Native من إجراء استدعاءات الدوال متعددة اللغات. يمكنك تنفيذ شيفرة أصيلة مخصصة من JavaScript والعكس. لسوء الحظ، بناءً على الجانب الذي نعمل عليه، نحقق نفس الهدف بطرق مختلفة. بالنسبة للجهة الأصيلة - نستخدم آلية الأحداث لجدولة تنفيذ دالة معالِجةٍ (handler function) في JavaScript، بينما بالنسبة لإطار React Native، فإننا نستدعي مباشرةً التوابع المصدَّرة من الوحدات الأصيلة. | ||
===استدعاء دوال React Native من المكون الأصيل (الأحداث)=== | ===استدعاء دوال React Native من المكون الأصيل (الأحداث)=== | ||
وُصفت الأحداث بالتفصيل في [ | وُصفت الأحداث بالتفصيل في [[ReactNative/native components ios#.D8.A7.D9.84.D8.A3.D8.AD.D8.AF.D8.A7.D8.AB .28Events.29|هذه الصفحة]]. لاحظ أن استخدام الأحداث لا يمنحنا أي ضمانات بشأن وقت التنفيذ (execution time)، إذ يعالج الحدث في سلسلة منفصلة. | ||
الأحداث مفيدة جدًّا، لأنها تسمح لنا بتغيير مكونات React Native دون الحاجة إلى أي مرجع يشير إليها. ومع ذلك، هناك بعض المشاكل التي قد تقع فيها أثناء استخدامها: | الأحداث مفيدة جدًّا، لأنها تسمح لنا بتغيير مكونات React Native دون الحاجة إلى أي مرجع يشير إليها. ومع ذلك، هناك بعض المشاكل التي قد تقع فيها أثناء استخدامها: | ||
سطر 79: | سطر 76: | ||
* لأنه يمكن إرسال الأحداث من أي مكان، فيمكنها إدخال تبعيات متشابكة -بشكل مربك- داخل مشروعك. | * لأنه يمكن إرسال الأحداث من أي مكان، فيمكنها إدخال تبعيات متشابكة -بشكل مربك- داخل مشروعك. | ||
* تشترك الأحداث في مجال الأسماء (namespace)، مما يعني أنك قد تواجه بعض تضارب الأسماء. لن تُكتشف التضاربات بشكل ساكن (statically)، مما يجعلها صعبة التنقيح (debug). | * تشترك الأحداث في مجال الأسماء (namespace)، مما يعني أنك قد تواجه بعض تضارب الأسماء. لن تُكتشف التضاربات بشكل ساكن (statically)، مما يجعلها صعبة التنقيح (debug). | ||
* إذا كنت تستخدم عدة نسخ لنفس مكون React Native وتريد تمييزها من منظور الحدث الخاص بك، فستحتاج على الأرجح إلى تقديم معرّفات (identifiers) وتمريرها مع الأحداث (يمكنك استخدام وسم <code>reactTag</code> الخاص | * إذا كنت تستخدم عدة نسخ لنفس مكون React Native وتريد تمييزها من منظور الحدث الخاص بك، فستحتاج على الأرجح إلى تقديم معرّفات (identifiers) وتمريرها مع الأحداث (يمكنك استخدام وسم <code>reactTag</code> الخاص بالواجهة الأصيلة كمعرّف) . | ||
يتمثل النمط الشائع الذي نستخدمه عند تضمين المكون الأصيل في React Native في جعل <code>RCTViewManager</code> للمكون الأصيل مفوَّضًا | يتمثل النمط الشائع الذي نستخدمه عند تضمين المكون الأصيل في React Native في جعل <code>RCTViewManager</code> للمكون الأصيل مفوَّضًا للواجهات، وإرسال الأحداث مرة أخرى إلى JavaScript عبر الجسر. هذا يُبقي استدعاءات الحدث ذات الصلة في مكان واحد. | ||
===استدعاء الدوال الأصيلة من React Native (الوحدات الأصيلة)=== | ===استدعاء الدوال الأصيلة من React Native (الوحدات الأصيلة)=== | ||
الوحدات الأصيلة هي أصناف Objective-C المتوفرة في JavaScript. تُنشأُ نسخة وحدة واحدة في كل جسر JavaScript. يمكنها تصدير دوال وثوابت اعتباطيّة إلى React Native. تمّت تغطيتها بالتفصيل في [ | الوحدات الأصيلة هي أصناف Objective-C المتوفرة في JavaScript. تُنشأُ نسخة وحدة واحدة في كل جسر JavaScript. يمكنها تصدير دوال وثوابت اعتباطيّة إلى React Native. تمّت تغطيتها بالتفصيل في [[ReactNative/native modules ios|هذه الصفحة]]. | ||
الوحدات الأصيلة هي وحداتٌ مفردة (singletons) ما يحدّ من الآلية (mechanism) في سياق التضمين (embedding). لنقل أن لدينا مكون React Native مُضمَّن في | الوحدات الأصيلة هي وحداتٌ مفردة (singletons) ما يحدّ من الآلية (mechanism) في سياق التضمين (embedding). لنقل أن لدينا مكون React Native مُضمَّن في واجهة أصيلة ونريد تحديث الواجهة الأب الأصيلة. باستخدام آلية الوحدة الأصيلة، سنُصدِّر دالةً لا تأخذ المعاملات المتوقعة فحسب، بل وأيضًا مُعرّفًا للواجهة الأب الأصيلة. سيُستخدم المعرِّف لاسترداد مرجعِِ (reference) إلى الواجهة الأب التي نريد تحديثها. ومع ذلك، سنحتاج إلى الاحتفاظ بارتباطٍ (mapping) من المعرِّفات إلى الواجهات الأصيلة في الوحدة. | ||
رغم أن هذا الحل معقد، إلا أنه يُستخدَم في <code>RCTUIManager</code>، وهو صنف React Native داخليّ يُدير جميع | رغم أن هذا الحل معقد، إلا أنه يُستخدَم في <code>RCTUIManager</code>، وهو صنف React Native داخليّ يُدير جميع واجهات React Native. | ||
يمكن أيضًا استخدام الوحدات الأصيلة لتوفير الوصول إلى المكتبات الأصيلة الموجودة مسبقا للغة JavaScript. مكتبة تحديد الموقع الجغرافي [https://github.com/facebook/react-native/tree/master/Libraries/Geolocation Geolocation library] مثال للفكرة. | يمكن أيضًا استخدام الوحدات الأصيلة لتوفير الوصول إلى المكتبات الأصيلة الموجودة مسبقا للغة JavaScript. مكتبة تحديد الموقع الجغرافي [https://github.com/facebook/react-native/tree/master/Libraries/Geolocation Geolocation library] مثال للفكرة. | ||
'''تحذير''': تشترك كافة الوحدات الأصيلة في نفس مجال الأسماء. احذر من تضارب الأسماء عند إنشاء وحدات جديدة. | '''تحذير''': تشترك كافة الوحدات الأصيلة في نفس مجال الأسماء. احذر من تضارب الأسماء عند إنشاء وحدات جديدة. | ||
==تدفق حساب التخطيط (Layout computation flow)== | ==تدفق حساب التخطيط (Layout computation flow)== | ||
سطر 99: | سطر 96: | ||
===تخطيط مكون أصيل مضمّن في React Native=== | ===تخطيط مكون أصيل مضمّن في React Native=== | ||
غطّينا هذه الحالة في [https://wiki.hsoub.com/ReactNative/native_components_ios هذه الصفحة]. نظرًا لأن جميع | غطّينا هذه الحالة في [https://wiki.hsoub.com/ReactNative/native_components_ios#.D8.A7.D9.84.D8.A3.D9.86.D9.85.D8.A7.D8.B7_.28Styles.29 هذه الصفحة]. نظرًا لأن جميع واجهات [[React]] الأصيلة لدينا هي أصناف فرعية من الصنف <code>UIView</code>، فمعظم سمات النمط والحجم (style and size) ستعمل كالمتوقع افتراضيا. | ||
===تخطيط مكون React Native مضمّن في المكون الأصيل=== | ===تخطيط مكون React Native مضمّن في المكون الأصيل=== | ||
====محتوى React Native ذو حجم ثابت==== | ====محتوى React Native ذو حجم ثابت==== | ||
أبسط سيناريو هو عندما يكون لدينا تطبيق React Native بحجم ثابتٍ معروفٍ عند الجانب الأصيل. على وجه الخصوص، | أبسط سيناريو هو عندما يكون لدينا تطبيق React Native بحجم ثابتٍ معروفٍ عند الجانب الأصيل. على وجه الخصوص، تقع واجهة React Native بوضع ملء الشاشة في هذه الدائرة. إذا أردنا واجهةً جذرًا أصغر، فيمكننا تعيين إطار RCTRootView بشكل صريح. | ||
على سبيل المثال، لجعل تطبيق React Native بارتفاع 200 بكسل (منطقي)، و اتّساع (width) | على سبيل المثال، لجعل تطبيق React Native بارتفاع 200 بكسل (منطقي)، و اتّساع (width) الواجهة (view) المضيف، يمكننا القيام بما يلي: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="objective-c"> | ||
// SomeViewController.m | // SomeViewController.m | ||
سطر 119: | سطر 116: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
عندما يكون لدينا | عندما يكون لدينا واجهة رئيسية (جذر root) بحجمٍ ثابتٍ، لابد من احترام حدوده على جانب JavaScript. بمعنى آخر، نحتاج إلى التأكد من إمكانية احتواء محتوى React Native في الواجهة الجذر ذي الحجم الثابت. أسهل طريقة لضمان ذلك هي استخدام تخطيط flexbox. إذا استخدمت التموضع المطلق (absolute positioning)، وكانت مكونات React مرئية خارج حدود الواجهة الجذر، فستحصل على تداخل مع واجهات المكون الأصيل، مما يؤدي إلى سلوك غير متوقع لبعض الميزات. على سبيل المثال، لن يُبرز <code>[[ReactNative/touchablehighlight|TouchableHighlight]]</code> لمساتك خارج حدود الواجهة الجذر. | ||
لا بأس بتحديث حجم | لا بأس بتحديث حجم الواجهة الجذر بشكل ديناميكي عن طريق إعادة ضبط خاصية إطاره (frame property). سيهتم React Native بتخطيط المحتوى. | ||
==== | ====محتوى React Native بحجم مرن==== | ||
في بعض الحالات، نود تصيير محتوى بحجم غير معروف مبدئيا. لنفترض أن الحجم سيُحدَّد ديناميكيًّا في JavaScript. لدينا حلان لهذه المشكلة: | في بعض الحالات، نود تصيير محتوى بحجم غير معروف مبدئيا. لنفترض أن الحجم سيُحدَّد ديناميكيًّا في JavaScript. لدينا حلان لهذه المشكلة: | ||
# يمكنك تغليف | # يمكنك تغليف واجهة React Native الخاص بك في مكون <code>[[ReactNative/scrollview|ScrollView]]</code>. هذا يضمن أن محتواك سيكون متاحًا دائمًا ولن يتداخل مع الواجهات الأصيلة. | ||
# يتيح لك React Native تحديد حجم تطبيق React Native في JavaScript وتزويده لمالك الصنف <code>RCTRootView</code> المُضيف. المالك هو المسؤول عن إعادة تخطيط | # يتيح لك React Native تحديد حجم تطبيق React Native في JavaScript وتزويده لمالك الصنف <code>RCTRootView</code> المُضيف. المالك هو المسؤول عن إعادة تخطيط الواجهات الفرعية والحفاظ على توافق واجهة المستخدم. نحقق هذا من خلال أوضاع مرونة (flexibility modes) الصنف <code>RCTRootView</code>. | ||
يدعم <code>RCTRootView</code> أربعة أوضاع مرونة مختلفة الحجم: | يدعم <code>RCTRootView</code> أربعة أوضاع مرونة مختلفة الحجم: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="objective-c"> | ||
// RCTRootView.h | // RCTRootView.h | ||
سطر 142: | سطر 139: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
القيمة <code>RCTRootViewSizeFlexibilityNone</code> هي الافتراضية، والتي تجعل حجم | القيمة <code>RCTRootViewSizeFlexibilityNone</code> هي الافتراضية، والتي تجعل حجم واجهةٍ جذرٍ ثابتًا (مع إمكانية تحديثه باستخدام <code>setFrame:</code>). تسمح لنا الأوضاع الثلاثة الأخرى بتتبع تحديثات حجم محتوى React Native. على سبيل المثال، سيؤدي تعيين الوضع إلى <code>RCTRootViewSizeFlexibilityHeight</code> إلى جعل React Native يقيس ارتفاع المحتوى وتمرير هذه المعلومات إلى مفوَّض <code>RCTRootView</code>. يمكن تنفيذ إجراء اعتباطيّ داخل المفوَّض، بما في ذلك تعيين إطار الواجهة الجذر، بحيث يتناسب المحتوى. يُستدعى المفوَّض فقط عندما يتغير حجم المحتوى. | ||
'''تحذير:''' يؤدي جعل بُعدِِ مرنًا في كل من JavaScript والمكون الأصيل إلى سلوك غير محدد. على سبيل المثال، لا تجعل | '''تحذير:''' يؤدي جعل بُعدِِ مرنًا في كل من JavaScript والمكون الأصيل إلى سلوك غير محدد. على سبيل المثال، لا تجعل واجهة مكون React ذو مستوى عالٍ مرنًا (مع <code>flexbox</code>) أثناء استخدام <code>RCTRootViewSizeFlexibilityWidth</code> على <code>RCTRootView</code> المستضيف. | ||
مثال: | مثال: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="objective-c"> | ||
// FlexibleSizeExampleView.m | // FlexibleSizeExampleView.m | ||
سطر 173: | سطر 170: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
في المثال، لدينا | في المثال، لدينا واجهة <code>FlexibleSizeExampleView</code> تحتوي على واجهةٍ جذرٍ. نقوم بإنشاء الواجهة الجذر وتهيئتها وتعيين المفوَّض. سيقوم المفوَّض بمعالجة تحديثات الحجم. بعد ذلك، نقوم بتعيين مرونة حجم الواجهة الجذر على <code>RCTRootViewSizeFlexibilityHeight</code>، مما يعني أنه سيتم استدعاء التابع <code>rootViewDidChangeIntrinsicSize:</code> في كل مرة يُغيِّر فيها محتوى React Native ارتفاعه. أخيرًا، نعيّن اتّساع أو عرض (width) الواجهة الجذر وموضعها. لاحظ أننا حددنا ارتفاعه أيضًا، لكن ليس له أي تأثير لأنّنا جعلنا الارتفاع معتمداً على React Native. | ||
يمكنك تفقد [https://github.com/facebook/react-native/blob/master/RNTester/RNTester/NativeExampleViews/FlexibleSizeExampleView.m شيفرة المصدر الكاملة للمثال هنا]. | يمكنك تفقد [https://github.com/facebook/react-native/blob/master/RNTester/RNTester/NativeExampleViews/FlexibleSizeExampleView.m شيفرة المصدر الكاملة للمثال هنا]. | ||
لا بأس في تغيير وضع مرونة حجم | لا بأس في تغيير وضع مرونة حجم الواجهة الجذر بشكل ديناميكي. سيؤدي تغيير وضع مرونة الواجهة الجذر إلى جدولة إعادة حساب تخطيطٍ (layout recalculation) و سيُستدعَى التابع المفوَّض <code>rootViewDidChangeIntrinsicSize:</code> بمجرد معرفة حجم المحتوى. | ||
'''ملاحظة:''' يُنفَّذ حساب تخطيط React Native | '''ملاحظة:''' يُنفَّذ حساب تخطيط React Native في خيط (thread) خاص، في حين أن تحديثات واجهة المستخدم UI للواجهة الأصيلة (native view) تؤدّى في الخيط الرئيسي (main thread). قد يتسبب هذا في عدم تناسق مؤقت في واجهة المستخدم بين الجهة الأصيلة وجهة React Native. هذه مشكلة معروفة ويعمل فريق React Native على مزامنة تحديثات واجهة المستخدم الواردة من مصادر مختلفة. | ||
'''ملاحظة:''' لا يُنفِّذ React Native أي حسابات تخطيط حتى | '''ملاحظة:''' لا يُنفِّذ React Native أي حسابات تخطيط حتى تصبح الواجهة الجذر الرئيسية واجهةً فرعيةً (subview) لبعض الواجهات الأخرى. إذا كنت تريد إخفاء واجهة React Native حتى تُعرف أبعادها، فأضف الواجهة الجذر كواجهة فرعية واجعلها مخفيةً مبدئيًّا (استخدم خاصية <code>hidden</code> الخاصة بالصنف <code>UIView</code>). ثم غيّر قابلية رؤيته (visibility) في التابع المفوَّض. | ||
== مصادر == | == مصادر == | ||
* [https://facebook.github.io/react-native/docs/communication-ios صفحة Communication between native and React Native في توثيق React Native الرسمي.] | * [https://facebook.github.io/react-native/docs/communication-ios صفحة Communication between native and React Native في توثيق React Native الرسمي.] | ||
[[تصنيف:ReactNative]] | [[تصنيف:ReactNative]] | ||
[[تصنيف:React Native Docs]] |
المراجعة الحالية بتاريخ 13:50، 9 أكتوبر 2021
يمكنك تعلّم كيفية تضمين React Native في مكون أصيل والعكس من صفحة الدمج مع تطبيقات قائمة وصفحة مكونات واجهة المستخدم الأصيلة. عندما نمزج المكونات الأصيلة ومكونات React Native، سنجد حتما الحاجة للتواصل بين هذين العالمين. ذكرنا بعض الطرق لتحقيق ذلك في أدلّة أخرى مسبقا. يلخص هذا المقال التقنيات المتاحة.
مقدمة
React Native مستوحًى من React، وبالتالي فإن الفكرة الأساسية لتدفق المعلومات (information flow) متشابهة. التدفق في React أحادي الاتجاه. نحافظ على تسلسل هرمي للمكونات، حيث يعتمد كل مكون فقط على مكوِّنه الأب وحالته الداخلية. وذلك باستعمال الخاصيات: تُمرَّر البيانات من أحد المكونات الآباء إلى مكوناته الأبناء من أعلى إلى أسفل. إذا كان أحد مكونات الأسلاف (ancestor component) يعتمد على حالة سليله (descendant)، فيجب أن تُمرَّر دالة رد نداء (callback) ليستخدمها المكون السليل لتحديث المكون السلف.
ينطبق نفس المفهوم على React Native. طالما نقوم ببناء تطبيقنا حصريا داخل إطار العمل، يمكننا التحكم بتطبيقنا بالخاصيات ودوال رد النداء. ولكن، عندما نخلط بين مكونات React Native والمكونات الأصيلة، فإننا نحتاج إلى بعض الآليات الخاصة متعددة اللغات (cross-language) التي ستسمح لنا بتمرير المعلومات بينها.
الخاصيات
الخاصيات هي أبسط طريقة للتواصل عبر المكونات. لذلك نحتاج إلى طريقة لتمرير الخاصيات من كل من المكون الأصيل إلى React Native، ومن React Native إلى المكون الأصيل.
تمرير الخاصيات من المكون الأصيل إلى React Native
لتضمين واجهة React Native في مكون أصيل، نستخدم RCTRootView
، وهو صنفٌ من النوع UIView
يحمل تطبيق React Native. كما يوفر واجهة بين الجانب الأصيل والتطبيق المستضاف.
يحتوي RCTRootView
على مُهيئ يسمح لك بتمرير خاصيات اعتباطيّة إلى تطبيق React Native. يجب أن يكون المعامل initialProperties
نسخةً (instance) من NSDictionary
. يُحوَّل القاموس داخليًا إلى كائن JSON الذي يمكن لمكون JavaScript ذي مستوى عال (top-level) الرجوع إليه.
NSArray *imageList = @[@"http://foo.com/bar1.png",
@"http://foo.com/bar2.png"];
NSDictionary *props = @{@"images" : imageList};
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"ImageBrowserApp"
initialProperties:props];
import React from 'react';
import { View, Image } from 'react-native';
export default class ImageBrowserApp extends React.Component {
renderImage(imgURI) {
return <Image source={{ uri: imgURI }} />;
}
render() {
return <View>{this.props.images.map(this.renderImage)}</View>;
}
}
يوفر RCTRootView
كذلك الخاصية appProperties
للقراءة والكتابة (read-write property). بعد تعيين appProperties
، يُعاد تصيير تطبيق React Native بخاصيات جديدة. يُنفَّذ التحديث فقط عندما تختلف الخاصيات المحدثة الجديدة عن الخاصيات السابقة.
NSArray *imageList = @[@"http://foo.com/bar3.png",
@"http://foo.com/bar4.png"];
rootView.appProperties = @{@"images" : imageList};
لا بأس في تحديث الخاصيات في أي وقت. ومع ذلك، يجب إجراء التحديثات على السلسلة الرئيسية. يمكنك استخدام دالة getter على أي سلسلة.
ملاحظة: هناك حاليًا مشكلة معروفة عند تعيين appProperties
أثناء بدء تشغيل الجسر، إذ يمكن فقدان التغيير. انظر هذه الصفحة لمزيد من المعلومات.
لا توجد وسيلة لتحديث بعض الخاصيات فقط في كل مرة. نقترح بناءه داخل غلاف (wrapper) خاص بك بدلاً من ذلك.
ملاحظة: حاليًا، لن تُستدعَى الدالة componentWillUpdateProps
في JavaScript لمكون React Native ذي المستوى الأعلى بعد تحديث خاصية ما. ومع ذلك، يمكنك الوصول إلى الخاصيات الجديدة داخل الدالة componentDidMount
.
تمرير الخاصيات من React Native إلى المكون الأصيل
تمّت تغطية مشكلة توفير الوصول لخاصيات المكونات الأصيلة بالتفصيل في هذه الصفحة. باختصار، صدِّر (export) الخاصيات باستخدام ماكرو RCT_CUSTOM_VIEW_PROPERTY
في مكونك الأصيل المخصص، ثم استخدمها في React Native كما لو كان المكون مُجرَّد مكونِ React Native عادي.
حدود الخاصيات
العيب الرئيسي للخاصيات متعددة اللغات هو أنها لا تدعم دوال رد النداء، والتي تسمح لنا بمعالجة ارتباطات البيانات من أسفل إلى أعلى. تخيل أن لديك واجهة React Native صغيرة تريد إزالتها من واجهة المكون الأصيل الأب كنتيجة لإجراءٍ في JavaScript. لا توجد طريقة للقيام بذلك باستخدام الخاصيات، لأن المعلومات ستحتاج إلى الانتقال من الأسفل إلى الأعلى.
رغم أن لدينا نوع من أنواع دوال رد النداء متعددة اللغات (الموصوفة هنا)، إلا أن دوال رد النداء هذه ليست دائمًا ما نحتاجه. المشكلة الرئيسية هي أنها غير مُصمَّمةٍ لتُمرَّر كخاصيات. بدلاً من ذلك، تسمح لنا هذه الآلية بتشغيل إجراء أصيل من JavaScript، والتعامل مع نتيجة هذا الإجراء في JavaScript.
طرق أخرى للتفاعلات متعددة اللغات (الأحداث والوحدات الأصيلة)
كما هو مذكور في الفصل السابق، يأتي استخدام الخاصيات مع بعض القيود. أحيانًا لا تكفي الخاصيات للتحكم بمنطق تطبيقنا، ونحتاج حلا يمنح مزيدًا من المرونة. يغطي هذا الفصل تقنيات تواصل أخرى متاحة في React Native. يمكن استخدامها للتواصل الداخلي (بين JavaScript والطبقات الأصيلة في React Native) وكذلك للتواصل الخارجي (بين React Native والجزء "الأصيل الخالص" من تطبيقك).
يمكّنك React Native من إجراء استدعاءات الدوال متعددة اللغات. يمكنك تنفيذ شيفرة أصيلة مخصصة من JavaScript والعكس. لسوء الحظ، بناءً على الجانب الذي نعمل عليه، نحقق نفس الهدف بطرق مختلفة. بالنسبة للجهة الأصيلة - نستخدم آلية الأحداث لجدولة تنفيذ دالة معالِجةٍ (handler function) في JavaScript، بينما بالنسبة لإطار React Native، فإننا نستدعي مباشرةً التوابع المصدَّرة من الوحدات الأصيلة.
استدعاء دوال React Native من المكون الأصيل (الأحداث)
وُصفت الأحداث بالتفصيل في هذه الصفحة. لاحظ أن استخدام الأحداث لا يمنحنا أي ضمانات بشأن وقت التنفيذ (execution time)، إذ يعالج الحدث في سلسلة منفصلة.
الأحداث مفيدة جدًّا، لأنها تسمح لنا بتغيير مكونات React Native دون الحاجة إلى أي مرجع يشير إليها. ومع ذلك، هناك بعض المشاكل التي قد تقع فيها أثناء استخدامها:
- لأنه يمكن إرسال الأحداث من أي مكان، فيمكنها إدخال تبعيات متشابكة -بشكل مربك- داخل مشروعك.
- تشترك الأحداث في مجال الأسماء (namespace)، مما يعني أنك قد تواجه بعض تضارب الأسماء. لن تُكتشف التضاربات بشكل ساكن (statically)، مما يجعلها صعبة التنقيح (debug).
- إذا كنت تستخدم عدة نسخ لنفس مكون React Native وتريد تمييزها من منظور الحدث الخاص بك، فستحتاج على الأرجح إلى تقديم معرّفات (identifiers) وتمريرها مع الأحداث (يمكنك استخدام وسم
reactTag
الخاص بالواجهة الأصيلة كمعرّف) .
يتمثل النمط الشائع الذي نستخدمه عند تضمين المكون الأصيل في React Native في جعل RCTViewManager
للمكون الأصيل مفوَّضًا للواجهات، وإرسال الأحداث مرة أخرى إلى JavaScript عبر الجسر. هذا يُبقي استدعاءات الحدث ذات الصلة في مكان واحد.
استدعاء الدوال الأصيلة من React Native (الوحدات الأصيلة)
الوحدات الأصيلة هي أصناف Objective-C المتوفرة في JavaScript. تُنشأُ نسخة وحدة واحدة في كل جسر JavaScript. يمكنها تصدير دوال وثوابت اعتباطيّة إلى React Native. تمّت تغطيتها بالتفصيل في هذه الصفحة.
الوحدات الأصيلة هي وحداتٌ مفردة (singletons) ما يحدّ من الآلية (mechanism) في سياق التضمين (embedding). لنقل أن لدينا مكون React Native مُضمَّن في واجهة أصيلة ونريد تحديث الواجهة الأب الأصيلة. باستخدام آلية الوحدة الأصيلة، سنُصدِّر دالةً لا تأخذ المعاملات المتوقعة فحسب، بل وأيضًا مُعرّفًا للواجهة الأب الأصيلة. سيُستخدم المعرِّف لاسترداد مرجعِِ (reference) إلى الواجهة الأب التي نريد تحديثها. ومع ذلك، سنحتاج إلى الاحتفاظ بارتباطٍ (mapping) من المعرِّفات إلى الواجهات الأصيلة في الوحدة.
رغم أن هذا الحل معقد، إلا أنه يُستخدَم في RCTUIManager
، وهو صنف React Native داخليّ يُدير جميع واجهات React Native.
يمكن أيضًا استخدام الوحدات الأصيلة لتوفير الوصول إلى المكتبات الأصيلة الموجودة مسبقا للغة JavaScript. مكتبة تحديد الموقع الجغرافي Geolocation library مثال للفكرة.
تحذير: تشترك كافة الوحدات الأصيلة في نفس مجال الأسماء. احذر من تضارب الأسماء عند إنشاء وحدات جديدة.
تدفق حساب التخطيط (Layout computation flow)
عند دمج المكونات الأصيلة وReact Native، نحتاج أيضًا إلى طريقة لدمج نظامي تخطيط (layout systems) مختلفين. يغطي هذا القسم مشاكل التخطيط الشائعة ويوفر وصفًا موجزًا للآليات اللازمة لمعالجتها.
تخطيط مكون أصيل مضمّن في React Native
غطّينا هذه الحالة في هذه الصفحة. نظرًا لأن جميع واجهات React الأصيلة لدينا هي أصناف فرعية من الصنف UIView
، فمعظم سمات النمط والحجم (style and size) ستعمل كالمتوقع افتراضيا.
تخطيط مكون React Native مضمّن في المكون الأصيل
محتوى React Native ذو حجم ثابت
أبسط سيناريو هو عندما يكون لدينا تطبيق React Native بحجم ثابتٍ معروفٍ عند الجانب الأصيل. على وجه الخصوص، تقع واجهة React Native بوضع ملء الشاشة في هذه الدائرة. إذا أردنا واجهةً جذرًا أصغر، فيمكننا تعيين إطار RCTRootView بشكل صريح.
على سبيل المثال، لجعل تطبيق React Native بارتفاع 200 بكسل (منطقي)، و اتّساع (width) الواجهة (view) المضيف، يمكننا القيام بما يلي:
// SomeViewController.m
- (void)viewDidLoad
{
[...]
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:appName
initialProperties:props];
rootView.frame = CGRectMake(0, 0, self.view.width, 200);
[self.view addSubview:rootView];
}
عندما يكون لدينا واجهة رئيسية (جذر root) بحجمٍ ثابتٍ، لابد من احترام حدوده على جانب JavaScript. بمعنى آخر، نحتاج إلى التأكد من إمكانية احتواء محتوى React Native في الواجهة الجذر ذي الحجم الثابت. أسهل طريقة لضمان ذلك هي استخدام تخطيط flexbox. إذا استخدمت التموضع المطلق (absolute positioning)، وكانت مكونات React مرئية خارج حدود الواجهة الجذر، فستحصل على تداخل مع واجهات المكون الأصيل، مما يؤدي إلى سلوك غير متوقع لبعض الميزات. على سبيل المثال، لن يُبرز TouchableHighlight
لمساتك خارج حدود الواجهة الجذر.
لا بأس بتحديث حجم الواجهة الجذر بشكل ديناميكي عن طريق إعادة ضبط خاصية إطاره (frame property). سيهتم React Native بتخطيط المحتوى.
محتوى React Native بحجم مرن
في بعض الحالات، نود تصيير محتوى بحجم غير معروف مبدئيا. لنفترض أن الحجم سيُحدَّد ديناميكيًّا في JavaScript. لدينا حلان لهذه المشكلة:
- يمكنك تغليف واجهة React Native الخاص بك في مكون
ScrollView
. هذا يضمن أن محتواك سيكون متاحًا دائمًا ولن يتداخل مع الواجهات الأصيلة. - يتيح لك React Native تحديد حجم تطبيق React Native في JavaScript وتزويده لمالك الصنف
RCTRootView
المُضيف. المالك هو المسؤول عن إعادة تخطيط الواجهات الفرعية والحفاظ على توافق واجهة المستخدم. نحقق هذا من خلال أوضاع مرونة (flexibility modes) الصنفRCTRootView
.
يدعم RCTRootView
أربعة أوضاع مرونة مختلفة الحجم:
// RCTRootView.h
typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {
RCTRootViewSizeFlexibilityNone = 0,
RCTRootViewSizeFlexibilityWidth,
RCTRootViewSizeFlexibilityHeight,
RCTRootViewSizeFlexibilityWidthAndHeight,
};
القيمة RCTRootViewSizeFlexibilityNone
هي الافتراضية، والتي تجعل حجم واجهةٍ جذرٍ ثابتًا (مع إمكانية تحديثه باستخدام setFrame:
). تسمح لنا الأوضاع الثلاثة الأخرى بتتبع تحديثات حجم محتوى React Native. على سبيل المثال، سيؤدي تعيين الوضع إلى RCTRootViewSizeFlexibilityHeight
إلى جعل React Native يقيس ارتفاع المحتوى وتمرير هذه المعلومات إلى مفوَّض RCTRootView
. يمكن تنفيذ إجراء اعتباطيّ داخل المفوَّض، بما في ذلك تعيين إطار الواجهة الجذر، بحيث يتناسب المحتوى. يُستدعى المفوَّض فقط عندما يتغير حجم المحتوى.
تحذير: يؤدي جعل بُعدِِ مرنًا في كل من JavaScript والمكون الأصيل إلى سلوك غير محدد. على سبيل المثال، لا تجعل واجهة مكون React ذو مستوى عالٍ مرنًا (مع flexbox
) أثناء استخدام RCTRootViewSizeFlexibilityWidth
على RCTRootView
المستضيف.
مثال:
// FlexibleSizeExampleView.m
- (instancetype)initWithFrame:(CGRect)frame
{
[...]
_rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"FlexibilityExampleApp"
initialProperties:@{}];
_rootView.delegate = self;
_rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight;
_rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0);
}
#pragma mark - RCTRootViewDelegate
- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView
{
CGRect newFrame = rootView.frame;
newFrame.size = rootView.intrinsicContentSize;
rootView.frame = newFrame;
}
في المثال، لدينا واجهة FlexibleSizeExampleView
تحتوي على واجهةٍ جذرٍ. نقوم بإنشاء الواجهة الجذر وتهيئتها وتعيين المفوَّض. سيقوم المفوَّض بمعالجة تحديثات الحجم. بعد ذلك، نقوم بتعيين مرونة حجم الواجهة الجذر على RCTRootViewSizeFlexibilityHeight
، مما يعني أنه سيتم استدعاء التابع rootViewDidChangeIntrinsicSize:
في كل مرة يُغيِّر فيها محتوى React Native ارتفاعه. أخيرًا، نعيّن اتّساع أو عرض (width) الواجهة الجذر وموضعها. لاحظ أننا حددنا ارتفاعه أيضًا، لكن ليس له أي تأثير لأنّنا جعلنا الارتفاع معتمداً على React Native.
يمكنك تفقد شيفرة المصدر الكاملة للمثال هنا.
لا بأس في تغيير وضع مرونة حجم الواجهة الجذر بشكل ديناميكي. سيؤدي تغيير وضع مرونة الواجهة الجذر إلى جدولة إعادة حساب تخطيطٍ (layout recalculation) و سيُستدعَى التابع المفوَّض rootViewDidChangeIntrinsicSize:
بمجرد معرفة حجم المحتوى.
ملاحظة: يُنفَّذ حساب تخطيط React Native في خيط (thread) خاص، في حين أن تحديثات واجهة المستخدم UI للواجهة الأصيلة (native view) تؤدّى في الخيط الرئيسي (main thread). قد يتسبب هذا في عدم تناسق مؤقت في واجهة المستخدم بين الجهة الأصيلة وجهة React Native. هذه مشكلة معروفة ويعمل فريق React Native على مزامنة تحديثات واجهة المستخدم الواردة من مصادر مختلفة.
ملاحظة: لا يُنفِّذ React Native أي حسابات تخطيط حتى تصبح الواجهة الجذر الرئيسية واجهةً فرعيةً (subview) لبعض الواجهات الأخرى. إذا كنت تريد إخفاء واجهة React Native حتى تُعرف أبعادها، فأضف الواجهة الجذر كواجهة فرعية واجعلها مخفيةً مبدئيًّا (استخدم خاصية hidden
الخاصة بالصنف UIView
). ثم غيّر قابلية رؤيته (visibility) في التابع المفوَّض.