الفرق بين المراجعتين لصفحة: «ReactNative/communication ios»

من موسوعة حسوب
أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:التواصل بين المكون الأصيل وReact Native}}</noinclude> يمكنك تعلّم كيفية تضمين React Native في م...'
 
طلا ملخص تعديل
 
(13 مراجعة متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:التواصل بين المكون الأصيل وReact Native}}</noinclude>
<noinclude>{{DISPLAYTITLE:التواصل بين المكون الأصيل وبين React Native في نظام iOS}}</noinclude>
 
يمكنك تعلّم كيفية تضمين React Native في مكون أصيل والعكس من صفحة [[ReactNative/integration with existing apps|الدمج مع تطبيقات قائمة]] وصفحة [[ReactNative/native components ios|مكونات واجهة المستخدم الأصيلة]]. عندما نمزج المكونات الأصيلة ومكونات React Native، سنجد حتما الحاجة للتواصل بين هذين العالمين. ذكرنا بعض الطرق لتحقيق ذلك في أدلّة أخرى مسبقا. يلخص هذا المقال التقنيات المتاحة.
يمكنك تعلّم كيفية تضمين React Native في مكون أصيل والعكس من صفحة الدمج مع تطبيقات قائمة وصفحة مكونات واجهة المستخدم الأصيلة. عندما نمزج المكونات الأصيلة ومكونات React Native، سنجد حتما الحاجة للتواصل بين هاذين العالمين. ذكرنا بعض الطرق لتحقيق ذلك في أدلّة أخرى مسبقا. يلخص هذا المقال التقنيات المتاحة.


==مقدمة==
==مقدمة==
React Native مستوحًى من React، وبالتالي فإن الفكرة الأساسية لتدفق المعلومات (information flow) متشابهة. التدفق في React أحادي الاتجاه. نحافظ على تسلسل هرمي للمكونات، حيث يعتمد كل مكون فقط على مكوِّنه الأب وحالته الداخلية. وذلك باستعمال الخاصيات: تُمرَّر البيانات من أحد المكونات الآباء إلى مكوناته الأبناء من أعلى إلى أسفل. إذا كان أحد مكونات الأسلاف (ancestor component) يعتمد على حالة سليله (descendant)، فيجب أن تُمرَّر دالة رد نداء (callback) ليستخدمها المكون السليل لتحديث المكون السلف.
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===
لتضمين عرض React Native في مكون أصيل، نستخدم RCTRootView، وهو صنفٌ من النوع UIView   تطبيق React Native. كما يوفر واجهة بين الجانب الأصيل والتطبيق المستضاف.
لتضمين واجهة React Native في مكون أصيل، نستخدم <code>RCTRootView</code>، وهو صنفٌ من النوع <code>UIView</code> يحمل تطبيق React Native. كما يوفر واجهة بين الجانب الأصيل والتطبيق المستضاف.


يحتوي RCTRootView على مُهيئ يسمح لك بتمرير خاصيات عشوائية إلى تطبيق React Native. يجب أن يكون المعامل initialProperties نسخةً (instance) من NSDictionary. يُحوَّل القاموس داخليًا إلى كائن JSON الذي يمكن لمكون JavaScript ذي مستوى عال (top-level) الرجوع إليه.
يحتوي <code>RCTRootView</code> على مُهيئ يسمح لك بتمرير خاصيات اعتباطيّة إلى تطبيق React Native. يجب أن يكون المعامل <code>initialProperties</code> نسخةً (instance) من <code>NSDictionary</code>. يُحوَّل القاموس داخليًا إلى كائن JSON الذي يمكن لمكون JavaScript ذي مستوى عال (top-level) الرجوع إليه.
<syntaxhighlight lang="javascript">
<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 {AppRegistry, View, Image} from 'react-native';
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:
   }
   }
}
}
AppRegistry.registerComponent('ImageBrowserApp', () => ImageBrowserApp);
</syntaxhighlight>
</syntaxhighlight>


يوفر RCTRootView كذلك الخاصية appProperties للقراءة والكتابة (read-write property). بعد تعيين appProperties، يُعاد تصيير تطبيق React Native بخاصيات جديدة. يُنفَّذ التحديث فقط عندما تختلف الخاصيات المحدثة الجديدة عن الخاصيات السابقة.
يوفر <code>RCTRootView</code> كذلك الخاصية <code>appProperties</code> للقراءة والكتابة (read-write property). بعد تعيين <code>appProperties</code>، يُعاد تصيير تطبيق React Native بخاصيات جديدة. يُنفَّذ التحديث فقط عندما تختلف الخاصيات المحدثة الجديدة عن الخاصيات السابقة.
<syntaxhighlight lang="javascript">
<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 على أي سلسلة.


'''ملاحظة''': هناك حاليًا مشكلة معروفة عند تعيين appProperties أثناء بدء تشغيل الجسر، يمكن فقد التغيير. انظر [https://github.com/facebook/react-native/issues/20115 هذه الصفحة] لمزيد من المعلومات.
'''ملاحظة''': هناك حاليًا مشكلة معروفة عند تعيين <code>appProperties</code> أثناء بدء تشغيل الجسر، إذ يمكن فقدان التغيير. انظر [https://github.com/facebook/react-native/issues/20115 هذه الصفحة] لمزيد من المعلومات.


لا توجد وسيلة لتحديث بعض الخاصيات فقط في كل مرة. نقترح بناءه داخل غلاف (wrapper) خاص بك بدلاً من ذلك.
لا توجد وسيلة لتحديث بعض الخاصيات فقط في كل مرة. نقترح بناءه داخل غلاف (wrapper) خاص بك بدلاً من ذلك.


'''ملاحظة''': حاليًا، لن تُستدعَى الدالة ‎‎<code>componentWillUpdateProps</code>‎‎ في JavaScript لمكون React Native ذي المستوى الأعلى بعد تحديث خاصية ما. ومع ذلك، يمكنك الوصول إلى الخاصيات الجديدة داخل الدالة componentDidMount.
'''ملاحظة''': حاليًا، لن تُستدعَى الدالة ‎‎<code>componentWillUpdateProps</code>‎‎ في JavaScript لمكون React Native ذي المستوى الأعلى بعد تحديث خاصية ما. ومع ذلك، يمكنك الوصول إلى الخاصيات الجديدة داخل الدالة <code>componentDidMount</code>.


===تمرير الخاصيات منReact Native إلى المكون الأصيل===
===تمرير الخاصيات من React Native إلى المكون الأصيل===


تمّت تغطية مشكلة توفير الوصول لخاصيات المكونات الأصيلة بالتفصيل في [https://wiki.hsoub.com/ReactNative/native_components_ios هذه الصفحة]. باختصار، صدِّر (export) الخاصيات باستخدام ماكرو RCT_CUSTOM_VIEW_PROPERTY في مكونك الأصيل المخصص، ثم استخدمها في React Native كما لو كان المكون مُجرَّد مكونِ 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. لا توجد طريقة للقيام بذلك باستخدام الخاصيات، لأن المعلومات ستحتاج إلى الانتقال من الأسفل إلى الأعلى.
العيب الرئيسي للخاصيات متعددة اللغات هو أنها لا تدعم دوال رد النداء، والتي تسمح لنا بمعالجة ارتباطات البيانات من أسفل إلى أعلى. تخيل أن لديك واجهة React Native صغيرة تريد إزالتها من واجهة المكون الأصيل الأب كنتيجة لإجراءٍ في JavaScript. لا توجد طريقة للقيام بذلك باستخدام الخاصيات، لأن المعلومات ستحتاج إلى الانتقال من الأسفل إلى الأعلى.


رغم أن لدينا نوع من أنواع دوال رد النداء متعددة اللغات ([https://wiki.hsoub.com/ReactNative/native_modules_ios الموصوفة هنا])، إلا أن دوال رد النداء هذه ليست دائمًا ما نحتاجه. المشكلة الرئيسية هي أنها غير مُصمَّمةٍ لتُمرَّر كخاصيات. بدلاً من ذلك، تسمح لنا هذه الآلية بتشغيل إجراء أصيل من JavaScript، والتعامل مع نتيجة هذا الإجراء في 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 والطبقات الأصيلة في React Native) وكذلك للتواصل الخارجي (بين React Native والجزء "الأصيل الخالص" من تطبيقك).


يمكّنك React Native من إجراء استدعاءات الدوال متعددة اللغات. يمكنك تنفيذ شيفرة أصيلة مخصصة من JavaScript والعكس. لسوء الحظ، بناءً على الجانب الذي نعمل عليه، نحقق نفس الهدف بطرق مختلفة. بالنسبة للجهة الأصيلة - نستخدم آلية الأحداث لجدولة تنفيذ دالة معالِجةٍ (handler function) في JavaScript، بينما بالنسبة لإطار React Native، فإننا نستدعي مباشرةً التوابع المصدَّرة من الوحدات الأصيلة.
يمكّنك React Native من إجراء استدعاءات الدوال متعددة اللغات. يمكنك تنفيذ شيفرة أصيلة مخصصة من JavaScript والعكس. لسوء الحظ، بناءً على الجانب الذي نعمل عليه، نحقق نفس الهدف بطرق مختلفة. بالنسبة للجهة الأصيلة - نستخدم آلية الأحداث لجدولة تنفيذ دالة معالِجةٍ (handler function) في JavaScript، بينما بالنسبة لإطار React Native، فإننا نستدعي مباشرةً التوابع المصدَّرة من الوحدات الأصيلة.


===استدعاء دوال React Native من المكون الأصيل (الأحداث)===
===استدعاء دوال React Native من المكون الأصيل (الأحداث)===
وُصفت الأحداث بالتفصيل في [https://wiki.hsoub.com/ReactNative/native_components_ios هذه الصفحة]. لاحظ أن استخدام الأحداث لا يمنحنا أي ضمانات بشأن وقت التنفيذ (execution time)، إذ يعالج الحدث في سلسلة منفصلة.
وُصفت الأحداث بالتفصيل في [[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) وتمريرها مع الأحداث (يمكنك استخدام وسم reactTag الخاص بالعرض الأصيل كمعرّف) .
* إذا كنت تستخدم عدة نسخ لنفس مكون React Native وتريد تمييزها من منظور الحدث الخاص بك، فستحتاج على الأرجح إلى تقديم معرّفات (identifiers) وتمريرها مع الأحداث (يمكنك استخدام وسم <code>reactTag</code> الخاص بالواجهة الأصيلة كمعرّف) .


يتمثل النمط الشائع الذي نستخدمه عند تضمين المكون الأصيل في React Native في جعل RCTViewManager للمكون الأصيل مفوَّضًا للعروض، وإرسال الأحداث مرة أخرى إلى JavaScript عبر الجسر. هذا يُبقي استدعاءات الحدث ذات الصلة في مكان واحد.
يتمثل النمط الشائع الذي نستخدمه عند تضمين المكون الأصيل في React Native في جعل <code>RCTViewManager</code> للمكون الأصيل مفوَّضًا للواجهات، وإرسال الأحداث مرة أخرى إلى JavaScript عبر الجسر. هذا يُبقي استدعاءات الحدث ذات الصلة في مكان واحد.


===استدعاء الدوال الأصيلة من React Native (الوحدات الأصيلة)===
===استدعاء الدوال الأصيلة من React Native (الوحدات الأصيلة)===


الوحدات الأصيلة هي أصناف Objective-C المتوفرة في JavaScript. تُنشأُ نسخة وحدة واحدة في كل جسر JavaScript. يمكنها تصدير دوال وثوابت اعتباطيّة إلى React Native.  تمّت تغطيتها بالتفصيل في [https://wiki.hsoub.com/ReactNative/native_modules_ios هذه الصفحة].
الوحدات الأصيلة هي أصناف Objective-C المتوفرة في JavaScript. تُنشأُ نسخة وحدة واحدة في كل جسر JavaScript. يمكنها تصدير دوال وثوابت اعتباطيّة إلى React Native.  تمّت تغطيتها بالتفصيل في [[ReactNative/native modules ios|هذه الصفحة]].


الوحدات  الأصيلة هي وحداتٌ مفردة (singletons) ما يحدّ من الآلية (mechanism) في سياق التضمين (embedding). لنقل أن لدينا مكون React Native مُضمَّن في عرض أصيل ونريد تحديث العرض الأب الأصيل. باستخدام آلية الوحدة الأصيلة، سنُصدِّر دالةً لا تأخذ المعاملات المتوقعة فحسب، بل وأيضًا مُعرّفاََ للعرض الأب الأصيل. سيُستخدم المعرِّف لاسترداد مرجعِِ (reference) إلى العرض الأب الذي نريد تحديثه. ومع ذلك، سنحتاج إلى الاحتفاظ بارتباطٍ (mapping) من المعرِّفات إلى العروض الأصيلة في الوحدة.
الوحدات  الأصيلة هي وحداتٌ مفردة (singletons) ما يحدّ من الآلية (mechanism) في سياق التضمين (embedding). لنقل أن لدينا مكون React Native مُضمَّن في واجهة أصيلة ونريد تحديث الواجهة الأب الأصيلة. باستخدام آلية الوحدة الأصيلة، سنُصدِّر دالةً لا تأخذ المعاملات المتوقعة فحسب، بل وأيضًا مُعرّفًا للواجهة الأب الأصيلة. سيُستخدم المعرِّف لاسترداد مرجعِِ (reference) إلى الواجهة الأب التي نريد تحديثها. ومع ذلك، سنحتاج إلى الاحتفاظ بارتباطٍ (mapping) من المعرِّفات إلى الواجهات الأصيلة في الوحدة.


رغم أن هذا الحل معقد، إلا أنه يُستخدَم في RCTUIManager، وهو صنف React Native داخليّ يُدير جميع عروض 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 هذه الصفحة]. نظرًا لأن جميع عروض React الأصيلة لدينا هي أصناف فرعية من الصنف UIView، فمعظم سمات النمط والحجم (style and size) ستعمل كالمتوقع افتراضيا.
غطّينا هذه الحالة في [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 بوضع ملء الشاشة في هذه الدائرة. إذا أردنا عرضًا جذرًا أصغر، فيمكننا تعيين إطار RCTRootView بشكل صريح.
أبسط سيناريو هو عندما يكون لدينا تطبيق React Native بحجم ثابتٍ معروفٍ عند الجانب الأصيل. على وجه الخصوص، تقع واجهة React Native بوضع ملء الشاشة في هذه الدائرة. إذا أردنا واجهةً جذرًا أصغر، فيمكننا تعيين إطار RCTRootView بشكل صريح.


على سبيل المثال، لجعل تطبيق React Native  بارتفاع 200 بكسل (منطقي)، و اتّساع (width) العرضِ (view) المضيف، يمكننا القيام بما يلي:
على سبيل المثال، لجعل تطبيق React Native  بارتفاع 200 بكسل (منطقي)، و اتّساع (width) الواجهة (view) المضيف، يمكننا القيام بما يلي:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// SomeViewController.m
// SomeViewController.m


سطر 119: سطر 116:
}
}
</syntaxhighlight>
</syntaxhighlight>
عندما يكون لدينا عرضٌ جذرٌ بحجمٍ ثابتٍ، لابد من احترام حدوده على جانب JavaScript. بمعنى آخر، نحتاج إلى التأكد من إمكانية احتواء محتوى React Native  في العرض الجذر ذي الحجم الثابت. أسهل طريقة لضمان ذلك هي استخدام تخطيط flexbox. إذا استخدمت التموضع المطلق (absolute positioning)، وكانت مكونات React مرئية خارج حدود العرض الجذر، فستحصل على تداخل مع عروض المكون الأصيل، مما يؤدي إلى سلوك غير متوقع لبعض الميزات. على سبيل المثال، لن يُبرز "TouchableHighlight" لمساتك خارج حدود العرض الجذر.
عندما يكون لدينا واجهة رئيسية (جذر root) بحجمٍ ثابتٍ، لابد من احترام حدوده على جانب JavaScript. بمعنى آخر، نحتاج إلى التأكد من إمكانية احتواء محتوى React Native  في الواجهة الجذر ذي الحجم الثابت. أسهل طريقة لضمان ذلك هي استخدام تخطيط flexbox. إذا استخدمت التموضع المطلق (absolute positioning)، وكانت مكونات React مرئية خارج حدود الواجهة الجذر، فستحصل على تداخل مع واجهات المكون الأصيل، مما يؤدي إلى سلوك غير متوقع لبعض الميزات. على سبيل المثال، لن يُبرز <code>[[ReactNative/touchablehighlight|TouchableHighlight]]</code> لمساتك خارج حدود الواجهة الجذر.


لا بأس بتحديث حجم العرض الجذر بشكل ديناميكي عن طريق إعادة ضبط خاصية إطاره (frame property). سيهتم React Native بتخطيط المحتوى.
لا بأس بتحديث حجم الواجهة الجذر بشكل ديناميكي عن طريق إعادة ضبط خاصية إطاره (frame property). سيهتم React Native بتخطيط المحتوى.


====محتوىReact Native بحجم مرن====
====محتوى React Native بحجم مرن====
في بعض الحالات، نود تصيير محتوى بحجم غير معروف مبدئيا. لنفترض أن الحجم سيُحدَّد ديناميكيًّا في JavaScript. لدينا حلان لهذه المشكلة:
في بعض الحالات، نود تصيير محتوى بحجم غير معروف مبدئيا. لنفترض أن الحجم سيُحدَّد ديناميكيًّا في JavaScript. لدينا حلان لهذه المشكلة:


# يمكنك تغليف عرض React Native  الخاص بك في مكون ScrollView. هذا يضمن أن محتواك  سيكون متاحًا دائمًا ولن يتداخل مع العروض الأصيلة.
# يمكنك تغليف واجهة React Native  الخاص بك في مكون <code>[[ReactNative/scrollview|ScrollView]]</code>. هذا يضمن أن محتواك  سيكون متاحًا دائمًا ولن يتداخل مع الواجهات الأصيلة.
# يتيح لك React Native تحديد حجم تطبيق React Native في JavaScript وتزويده لمالك الصنف RCTRootView المُضيف. المالك هو المسؤول عن إعادة تخطيط العروض الفرعية والحفاظ على توافق واجهة المستخدم. نحقق هذا من خلال أوضاع مرونة (flexibility modes) الصنف RCTRootView.
# يتيح لك React Native تحديد حجم تطبيق React Native في JavaScript وتزويده لمالك الصنف <code>RCTRootView</code> المُضيف. المالك هو المسؤول عن إعادة تخطيط الواجهات الفرعية والحفاظ على توافق واجهة المستخدم. نحقق هذا من خلال أوضاع مرونة (flexibility modes) الصنف <code>RCTRootView</code>.


يدعم RCTRootView أربعة أوضاع مرونة مختلفة الحجم:
يدعم <code>RCTRootView</code> أربعة أوضاع مرونة مختلفة الحجم:


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RCTRootView.h
// RCTRootView.h


سطر 142: سطر 139:
</syntaxhighlight>
</syntaxhighlight>


القيمة RCTRootViewSizeFlexibilityNone هي الافتراضية، والتي تجعل حجم عرضٍ جذرٍ ثابتًا (مع إمكانية تحديثه باستخدام ‎‎<code>setFrame:</code>‎‎). تسمح لنا الأوضاع الثلاثة الأخرى بتتبع تحديثات حجم محتوى  React Native . على سبيل المثال، سيؤدي تعيين الوضع إلى RCTRootViewSizeFlexibilityHeight إلى جعل React Native يقيس ارتفاع المحتوى وتمرير هذه المعلومات إلى مفوَّض RCTRootView. يمكن تنفيذ إجراء اعتباطيّ داخل المفوَّض، بما في ذلك تعيين إطار العرض الجذر، بحيث يتناسب المحتوى. يُستدعى المفوَّض فقط عندما يتغير حجم المحتوى.
القيمة <code>RCTRootViewSizeFlexibilityNone</code> هي الافتراضية، والتي تجعل حجم واجهةٍ جذرٍ ثابتًا (مع إمكانية تحديثه باستخدام ‎‎<code>setFrame:</code>‎‎). تسمح لنا الأوضاع الثلاثة الأخرى بتتبع تحديثات حجم محتوى  React Native. على سبيل المثال، سيؤدي تعيين الوضع إلى <code>RCTRootViewSizeFlexibilityHeight</code> إلى جعل React Native يقيس ارتفاع المحتوى وتمرير هذه المعلومات إلى مفوَّض <code>RCTRootView</code>. يمكن تنفيذ إجراء اعتباطيّ داخل المفوَّض، بما في ذلك تعيين إطار الواجهة الجذر، بحيث يتناسب المحتوى. يُستدعى المفوَّض فقط عندما يتغير حجم المحتوى.


'''تحذير:''' يؤدي جعل بُعدِِ مرنًا في كل من JavaScript والمكون الأصيل إلى سلوك غير محدد. على سبيل المثال، لا تجعل عرض مكون React ذو مستوى عالٍ مرنًا (مع flexbox) أثناء استخدام RCTRootViewSizeFlexibilityWidth على RCTRootView المستضيف.
'''تحذير:''' يؤدي جعل بُعدِِ مرنًا في كل من JavaScript والمكون الأصيل إلى سلوك غير محدد. على سبيل المثال، لا تجعل واجهة مكون React ذو مستوى عالٍ مرنًا (مع <code>flexbox</code>) أثناء استخدام <code>RCTRootViewSizeFlexibilityWidth</code> على <code>RCTRootView</code> المستضيف.


مثال:
مثال:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// FlexibleSizeExampleView.m
// FlexibleSizeExampleView.m


سطر 173: سطر 170:
</syntaxhighlight>
</syntaxhighlight>


في المثال، لدينا عرض FlexibleSizeExampleView المحتوِي على عرضٍ جذرٍ. نقوم بإنشاء العرض الجذر وتهيئته وتعيين المفوَّض. سيقوم المفوَّض بمعالجة تحديثات الحجم. بعد ذلك، نقوم بتعيين مرونة حجم العرض الجذر على RCTRootViewSizeFlexibilityHeight، مما يعني أنه سيتم استدعاء التابع ‎‎<code>rootViewDidChangeIntrinsicSize:</code>‎‎ في كل مرة يُغيِّر فيها محتوى React Native ارتفاعه. أخيرًا، نعيّن اتّساع (width) العرض الجذر وموضعه. لاحظ أننا حددنا ارتفاعه أيضًا، لكن ليس له أي تأثير لأنّنا جعلنا الارتفاع معتمداً على React Native.
في المثال، لدينا واجهة <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>‎‎ بمجرد معرفة حجم المحتوى.
لا بأس في تغيير وضع مرونة حجم الواجهة الجذر بشكل ديناميكي. سيؤدي تغيير وضع مرونة الواجهة الجذر إلى جدولة إعادة حساب تخطيطٍ (layout recalculation) و سيُستدعَى التابع المفوَّض ‎‎<code>rootViewDidChangeIntrinsicSize:</code>‎‎ بمجرد معرفة حجم المحتوى.


'''ملاحظة:'''  يُنفَّذ حساب تخطيط React Native في سلسلة (thread) خاصة، في حين أن تحديثات عرض واجهة المستخدم في الجهة الأصيلة تؤدّى في السلسلة الرئيسية. قد يتسبب هذا في عدم تناسق مؤقت في واجهة المستخدم بين الجهة الأصيلة وجهة React Native. هذه مشكلة معروفة. ويعمل فريق React Native على مزامنة تحديثات واجهة المستخدم الواردة من مصادر مختلفة.
'''ملاحظة:'''  يُنفَّذ حساب تخطيط React Native في خيط (thread) خاص، في حين أن تحديثات واجهة المستخدم UI للواجهة الأصيلة (native view) تؤدّى في الخيط الرئيسي (main thread). قد يتسبب هذا في عدم تناسق مؤقت في واجهة المستخدم بين الجهة الأصيلة وجهة React Native. هذه مشكلة معروفة ويعمل فريق React Native على مزامنة تحديثات واجهة المستخدم الواردة من مصادر مختلفة.


'''ملاحظة:''' لا يُنفِّذ React Native أي حسابات تخطيط حتى يصبح العرض الجذر عرضا فرعيا (subview) لبعض العروض الأخرى. إذا كنت تريد إخفاء عرض React Native حتى تُعرف أبعاده، فأضف العرض الجذر كعرض فرعي واجعله مخفيًّا مبدئيًّا (استخدم خاصية hidden الخاصة بالصنف UIView). ثم غيّر قابلية رؤيته (visibility) في التابع المفوَّض.
'''ملاحظة:''' لا يُنفِّذ 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. لدينا حلان لهذه المشكلة:

  1. يمكنك تغليف واجهة React Native الخاص بك في مكون ScrollView. هذا يضمن أن محتواك سيكون متاحًا دائمًا ولن يتداخل مع الواجهات الأصيلة.
  2. يتيح لك 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) في التابع المفوَّض.

مصادر