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

من موسوعة حسوب
طلا ملخص تعديل
 
(4 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE: مكونات واجهة المستخدم الأصيلة (Android) في React Native}}</noinclude>
<noinclude>{{DISPLAYTITLE: مكونات واجهة المستخدم الأصيلة لنظام Android في React Native}}</noinclude>
 
هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل <code>[[ReactNative/scrollview|ScrollView]]</code> و <code>[[ReactNative/textinput|TextInput]]</code>، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.
هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل <code>[[ReactNative/scrollview|ScrollView]]</code> و<code>[[ReactNative/textinput|TextInput]]</code>، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.


على غرار صفحة [[ReactNative/native modules android|الوحدات الأصيلة]]، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة Android SDK. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون <code>ImageView</code> الموجود في مكتبة React Native الأساسية (the core React Native library).
على غرار صفحة [[ReactNative/native modules android|الوحدات الأصيلة]]، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة Android SDK. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون <code>ImageView</code> الموجود في مكتبة React Native الأساسية (the core React Native library).
==ImageView كمثال==
==الواجهة ImageView كمثال==
في هذا المثال، سنتعرف على متطلبات الإجراء (implementation requirements) للسماح باستخدام <code>ImageView</code> في JavaScript.
في هذا المثال، سنتعرف على متطلبات الإجراء (implementation requirements) للسماح باستخدام ImageView في JavaScript.


تُنشأ العروض الأصيلة وتُعالَج عن طريق توسيع <code>ViewManager</code> أو <code>SimpleViewManager</code> بشكل أكثر شيوعًا. يعتبر <code>SimpleViewManager</code> مناسبًا في هذه الحالة لأنه يطبق خاصيات شائعة مثل لون الخلفية والتعتيم وتخطيط Flexbox.
تُنشأ الواجهات الأصيلة Native views وتُعالَج عن طريق توسيع <code>ViewManager</code> أو <code>SimpleViewManager</code> بشكل أكثر شيوعًا. يعتبر <code>SimpleViewManager</code> مناسبًا في هذه الحالة لأنه يطبق خاصيات شائعة مثل لون الخلفية والتعتيم وتخطيط Flexbox.


هذه الأصناف الفرعية هي مفردات (singletons) أساسا، أي تُنشأ نسخة واحدة فقط لكل منها بواسطة الجسر. وهي تُزوِّد العروضَ الأصيلة إلى <code>NativeViewHierarchyManager</code>، والذي يُفوِّض لها مرة أخرى لتعيين وتحديث خاصيات العروض حسب الضرورة. عادةً ما يكون مدراء العروض (أصناف <code>ViewManager</code>) مفوَّضين للعروض، مُرسلةً الأحداث مرة أخرى إلى JavaScript عبر الجسر.
هذه الأصناف الفرعية هي مفردات (singletons) أساسا، أي تُنشأ نسخة واحدة فقط لكل منها بواسطة الجسر. وهي تُزوِّد الواجهات الأصيلة إلى <code>NativeViewHierarchyManager</code>، والذي يُفوِّض لها مرة أخرى لتعيين وتحديث خاصيات الواجهات حسب الضرورة. عادةً ما يكون مدراء الواجهات (أصناف <code>ViewManager</code>) مفوَّضين للواجهات، مُرسلةً الأحداث مرة أخرى إلى JavaScript عبر الجسر.


تزويدُ عرضِِ (Vending a view) أمرٌ بسيط:
تزويدُ واجهة (Vending a view) أمرٌ بسيط:


# إنشاء الصنف الفرعي لمدير العرض <code>ViewManager</code>
# إنشاء الصنف الفرعي لمدير الواجهة ViewManager
# كتابة التابع  <code>createViewInstance</code>
# كتابة التابع  <code>createViewInstance</code>
# توفير الوصول إلى توابع setter لضبط خاصيّات العرض باستخدام التوصيف ‎‎<code>@ReactProp</code>‎‎ (‎‎أو ‎‎<code>@ReactPropGroup</code>‎‎)
# توفير الوصول إلى توابع setter لضبط خاصيّات الواجهة باستخدام التوصيف ‎‎<code>@ReactProp</code>‎‎ (‎‎أو ‎‎<code>@ReactPropGroup</code>‎‎)
# تسجيل المدير في <code>createViewManagers</code> الخاص بحزمة التطبيقات.
# تسجيل المدير في <code>createViewManagers</code> الخاص بحزمة التطبيقات.
#  كتابة وحدة JavaScript
#  كتابة وحدة JavaScript


===1. إنشاء الصنف الفرعي لمدير العرض <code>ViewManager</code>===
===1. إنشاء الصنف الفرعي لمدير الواجهة <code>ViewManager</code>===
في هذا المثال، سننشئ صنف مدير العرض <code>ReactImageManager</code> الذي يوسِّع <code>SimpleViewManager</code> من النوع <code>ReactImageView</code>. النوع <code>ReactImageView</code> هو نوع الكائن الذي يديره المدير، وسيكون هذا هو العرض الأصيل المخصص. يُستخدَم الاسم المعاد من طرف <code>getName</code> لإحالة (to reference) نوع العرض الأصيل من JavaScript.
في هذا المثال، سننشئ صنف مدير الواجهة <code>ReactImageManager</code> الذي يوسِّع <code>SimpleViewManager</code> من النوع <code>ReactImageView</code>. النوع <code>ReactImageView</code> هو نوع الكائن الذي يديره المدير، وسيكون هذا هو الواجهة الأصيلة المخصصة. يُستخدَم الاسم المعاد من طرف <code>getName</code> لإحالة (to reference) نوع الواجهة الأصيلة من JavaScript.
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
...
...
سطر 27: سطر 26:


   public static final String REACT_CLASS = "RCTImageView";
   public static final String REACT_CLASS = "RCTImageView";
  ReactApplicationContext mCallerContext;
  public ReactImageManager(ReactApplicationContext reactContext) {
    mCallerContext = reactContext;
  }


   @Override
   @Override
سطر 35: سطر 39:


===2. كتابة التابع <code>createViewInstance</code>===
===2. كتابة التابع <code>createViewInstance</code>===
تنشأ العروض في تابع <code>createViewInstance</code>، يجب أن يُهيِّئَ العرض نفسه في حالته الافتراضية (default state)، وستُعيَّن الخاصيات عبر استدعاء  <code>updateView</code> لاحقًا.
تنشأ الواجهات في تابع <code>createViewInstance</code>، يجب أن يُهيِّئَ الواجهة نفسها في حالته الافتراضية (default state)، وستُعيَّن الخاصيات عبر استدعاء  <code>updateView</code> لاحقًا.
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
@Override
  @Override
   public ReactImageView createViewInstance(ThemedReactContext context) {
   public ReactImageView createViewInstance(ThemedReactContext context) {
     return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), mCallerContext);
     return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, mCallerContext);
   }
   }
</syntaxhighlight>
</syntaxhighlight>


===3. توفير الوصول إلى توابع setter لضبط خاصيّات العرض باستخدام التوصيف ‎‎<code>@ReactProp</code>‎‎ (‎‎أو ‎<code>@ReactPropGroup</code>‎‎)===
===3. توفير الوصول إلى توابع ضابطة لضبط خاصيات الواجهة باستخدام التوصيف ‎‎<code>@ReactProp</code>‎‎ (‎‎أو ‎<code>@ReactPropGroup</code>‎‎)===


يجب توفير الوصول للخاصيّات التي يجب أن تنعكس في JavaScript كتابع setter موصوف (annotated) باستخدام التوصيف ‎‎<code>@ReactProp</code>‎‎ (‎‎أو ‎<code>@ReactPropGroup</code>‎‎). يجب أن يأخذ تابع Setter  العرض المراد تحديثه (من نوع العرض الحالي) كأوّل معامل، وقيمة الخاصية كمعامل ثانٍ. يجب إعلان Setter كتابع <code>void</code> ويجب أن يكون عامًّا (<code>public</code>).  يُحدّد نوع الخاصية المرسلة إلى JavaScript تلقائيًا استنادًا إلى نوع المعامل القيمة الخاص بتابع setter. أنواع القيم المدعومة حاليًا هي: ‎‎<code>boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap</code>‎‎.
يجب توفير الوصول للخاصيّات التي يجب أن تنعكس في JavaScript كتابع setter موصوف (annotated) باستخدام التوصيف ‎‎<code>@ReactProp</code>‎‎ (‎‎أو ‎<code>@ReactPropGroup</code>‎‎). يجب أن يأخذ تابع ضابط Setter  الواجهة المراد تحديثها (من نوع الواجهة الحالية) كأوّل معامل، وقيمة الخاصية كمعامل ثانٍ. يجب إعلان الضابط Setter كتابع <code>void</code> ويجب أن يكون عامًّا (<code>public</code>).  يُحدّد نوع الخاصية المرسلة إلى JavaScript تلقائيًا استنادًا إلى نوع المعامل القيمة الخاص بتابع setter. أنواع القيم المدعومة حاليًا هي:


يحتاج التوصيف ‎‎<code>@ReactProp</code>‎‎ معاملًا إلزاميًّا واحدًا يُسمّى <code>name</code> من النوع <code>String</code> يمثّل اسم التوصيف. يُستخدَم الاسم المعيَّن للتوصيف ‎‎<code>@ReactProp</code>‎‎ المرتبط بالتابع setter للإشارة إلى الخاصية في جانب JavaScript.
‎‎<syntaxhighlight lang="js">
boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap‎‎
</syntaxhighlight>يحتاج التوصيف ‎‎<code>@ReactProp</code>‎‎ معاملًا إلزاميًّا واحدًا يُسمّى <code>name</code> من النوع <code>String</code> يمثّل اسم التوصيف. يُستخدَم الاسم المعيَّن للتوصيف ‎‎<code>@ReactProp</code>‎‎ المرتبط بالتابع الضابط setter للإشارة إلى الخاصية في جانب JavaScript.


باستثناء <code>name</code>، يقبل التوصيف ‎‎<code>@ReactProp</code>‎‎ المعاملات الاختيارية التالية: <code>defaultBoolean</code>، و<code>defaultInt</code>، و<code>defaultFloat</code>. يجب أن تكون هذه المعاملات من النوع البدائي المقابل (بالتالي <code>boolean</code>، و<code>int</code>، و<code>float</code>) وستمرَّر القيمة المعطاة إلى تابع setter في حالة إزالة الخاصية التي يشير إليها تابع setter من المكون. لاحظ أن القيم "الافتراضية (<code>"default"</code>)" موفَّرة فقط للأنواع البدائية، إذا كان تابع setter نوعاً معقداً، ستوفَّر القيمة <code>null</code>  كقيمة افتراضية في حالة إزالة الخاصية المطابقة.
باستثناء <code>name</code>، يقبل التوصيف ‎‎<code>@ReactProp</code>‎‎ المعاملات الاختيارية التالية: <code>defaultBoolean</code>، و<code>defaultInt</code>، و<code>defaultFloat</code>. يجب أن تكون هذه المعاملات من النوع البدائي المقابل (بالتالي <code>boolean</code>، و<code>int</code>، و<code>float</code>) وستمرَّر القيمة المعطاة إلى تابع ضابط setter في حالة إزالة الخاصية التي يشير إليها تابع ضابط setter من المكون. لاحظ أن القيم "الافتراضية ("default") موفَّرة فقط للأنواع البدائية، إذا كان التابع الضابط setter نوعًا معقدًا، ستوفَّر القيمة <code>null</code>  كقيمة افتراضية في حالة إزالة الخاصية المطابقة.


متطلبات إعلان (declaration) تابع Setter للتوابع الموصوفة بالتوصيف ‎‎<code>@ReactPropGroup</code>‎‎ مختلفة عن متطلبات ‎‎<code>@ReactProp</code>‎‎، يرجى الرجوع إلى توثيقات صنف التوصيف ‎‎<code>@ReactPropGroup</code>‎‎ للمزيد من المعلومات حول هذا الموضوع.
متطلبات إعلان (declaration) تابع ضابط Setter للتوابع الموصوفة بالتوصيف ‎‎<code>@ReactPropGroup</code>‎‎ مختلفة عن متطلبات ‎‎<code>@ReactProp</code>‎‎، يرجى الرجوع إلى توثيقات صنف التوصيف ‎‎<code>@ReactPropGroup</code>‎‎ للمزيد من المعلومات حول هذا الموضوع.


'''ملاحظة مهمّة!''' في ReactJS تحديث قيمة الخاصية سيؤدي إلى استدعاء تابع setter. لاحظ أن إحدى الطرق لتحديث المكون هي بإزالة الخاصيات التي عُيِّنت من قبل. في هذه الحالة، سيستدعى تابع setter كذلك  لإشعار مدير العرض بأن الخاصية قد تغيرت. في هذه الحالة، ستوفَّر القيمة "الافتراضية" (للأنواع البدائية، يُمكن تحديد القيم "الافتراضيّة" باستخدام <code>defaultBoolean</code>، و<code>defaultFloat</code>، إلخ. والتي هي معاملات التوصيف ‎‎<code>@ReactProp</code>‎‎، للأنواع المعقَّدة، سيُستدعى setter مع القيمة <code>null</code>).
'''ملاحظة مهمّة:''' في ReactJS تحديث قيمة الخاصية سيؤدي إلى استدعاء تابع ضباط setter. لاحظ أن إحدى الطرق لتحديث المكون هي بإزالة الخاصيات التي عُيِّنت من قبل. في هذه الحالة، سيستدعى تابع ضابط setter كذلك  لإشعار مدير الواجهة بأن الخاصية قد تغيرت. في هذه الحالة، ستوفَّر القيمة "الافتراضية" (للأنواع البدائية، يُمكن تحديد القيم "الافتراضيّة" باستخدام <code>defaultBoolean</code>، و<code>defaultFloat</code>، إلخ. والتي هي معاملات التوصيف ‎‎<code>@ReactProp</code>‎‎، للأنواع المعقَّدة، سيُستدعى الضابط setter مع القيمة <code>null</code>).
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  @ReactProp(name = "src")
@ReactProp(name = "src")
   public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
   public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
     view.setSource(sources);
     view.setSource(sources);
سطر 70: سطر 76:
   }
   }
</syntaxhighlight>
</syntaxhighlight>
===4. تسجيل مدير العرض <code>ViewManager</code>===
===4. تسجيل مدير الواجهة <code>ViewManager</code>===
الخطوة الأخيرة في Java هي تسجيل مدير العرض للتطبيق، وهذا يحدث بطريقة مشابهة [https://wiki.hsoub.com/ReactNative/native_modules_android للوحدات الأصيلة] عبر دالة عضو حزمة (package member function) التطبيق <code>createViewManagers</code>.
الخطوة الأخيرة في Java هي تسجيل مدير الواجهة للتطبيق، وهذا يحدث بطريقة مشابهة [https://wiki.hsoub.com/ReactNative/native_modules_android للوحدات الأصيلة] عبر دالة عضو حزمة (package member function) التطبيق <code>createViewManagers</code>.
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  @Override
@Override
   public List<ViewManager> createViewManagers(
   public List<ViewManager> createViewManagers(
                             ReactApplicationContext reactContext) {
                             ReactApplicationContext reactContext) {
     return Arrays.<ViewManager>asList(
     return Arrays.<ViewManager>asList(
       new ReactImageManager()
       new ReactImageManager(reactContext)
     );
     );
   }
   }
سطر 83: سطر 89:


===5. إنشاء وحدة JavaScript===
===5. إنشاء وحدة JavaScript===
الخطوة الأخيرة هي إنشاء وحدة JavaScript تحدد طبقة الواجهة (interface layer) بين Java و JavaScript لمستخدمي عرضك الجديد. يوصى بتوثيق واجهة المكون في هذه الوحدة (باستخدام Flow أو TypeScript أو التعليقات مثلًا).
الخطوة الأخيرة هي إنشاء وحدة JavaScript تحدد طبقة الواجهة (interface layer) بين Java و JavaScript لمستخدمي الواجهة الجديدة. يوصى بتوثيق واجهة المكون في هذه الوحدة (باستخدام Flow أو [[TypeScript]] أو التعليقات مثلًا).
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
// ImageView.js
// ImageView.js


import {requireNativeComponent} from 'react-native';
import { requireNativeComponent } from 'react-native';


/**
/**
سطر 98: سطر 104:
module.exports = requireNativeComponent('RCTImageView');
module.exports = requireNativeComponent('RCTImageView');
</syntaxhighlight>
</syntaxhighlight>
تأخذ الدالة <code>requireNativeComponent</code> اسم العرض الأصيل. لاحظ أنه إذا احتاج مكونك يحتاج إلى القيام بأي شيء أعقد (مثل معالجة الأحداث المخصصة)، فعليك تغليف المكون الأصيل في مكون React آخر. هذا موضح في مثال <code>MyCustomView</code> أدناه.
تأخذ الدالة <code>requireNativeComponent</code> اسم الواجهة الأصيلة. لاحظ أنه إذا احتاج مكونك إلى القيام بأي شيء أعقد (مثل معالجة الأحداث المخصصة)، فعليك تغليف المكون الأصيل في مكون React آخر. هذا موضح في مثال <code>MyCustomView</code> أدناه.


==الأحداث (Events)==
==الأحداث (Events)==
نعرف الآن كيف نوفر الوصول لمكونات العرض الأصيل التي يمكننا التحكم فيها بسهولة من JavaScript، لكن كيف نتعامل مع الأحداث من المستخدم، مثل القرص للتكبير أو للتصغير أو المسح (panning). عند حدوثِ حدثٍ أصيل، يجب أن تصدر الشيفرة الأصيلة حدثًا للعرض في JavaScript، مع ربط العرضين بالقيمة المُعادة من تابع ‎‎<code>getId()</code>‎‎.
نعرف الآن كيف نوفر الوصول لمكونات الواجهة الأصيلة التي يمكننا التحكم فيها بسهولة من JavaScript، لكن كيف نتعامل مع الأحداث من المستخدم، مثل القرص للتكبير أو للتصغير أو المسح (panning). عند حدوثِ حدثٍ أصيل، يجب أن تصدر الشيفرة الأصيلة حدثًا للواجهة في JavaScript، مع ربط الواجهتين بالقيمة المُعادة من تابع ‎‎<code>getId()</code>‎‎.
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
class MyCustomView extends View {
class MyCustomView extends View {
سطر 117: سطر 123:
</syntaxhighlight>
</syntaxhighlight>


لربط اسم حدث <code>topChange</code> إلى خاصية دالة رد النداء <code>onChange</code> في JavaScript، سجِّله عن طريق تجاوز تابع ‎‎<code>getExportedCustomBubblingEventTypeConstants</code>‎‎ في مدير العرض <code>ViewManager</code> الخاص بك:
لربط اسم حدث <code>topChange</code> مع خاصية دالة رد النداء <code>onChange</code> في JavaScript، سجِّله عن طريق تجاوز تابع ‎‎<code>getExportedCustomBubblingEventTypeConstants</code>‎‎ في مدير الواجهة <code>ViewManager</code> الخاص بك:
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
public class ReactImageManager extends SimpleViewManager<MyCustomView> {
public class ReactImageManager extends SimpleViewManager<MyCustomView> {
سطر 135: سطر 141:
تُستدعَى دالة رد النداء هذه مع الحدث الخام (raw event)، والذي نقوم بمعالجته عادةً في المكون المُغلِّف (wrapper component) لإنشاء واجهة برمجة تطبيقات أبسط:
تُستدعَى دالة رد النداء هذه مع الحدث الخام (raw event)، والذي نقوم بمعالجته عادةً في المكون المُغلِّف (wrapper component) لإنشاء واجهة برمجة تطبيقات أبسط:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
// MyCustomView.js
// MyCustomView.js


سطر 162: سطر 169:
var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`);
var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`);
</syntaxhighlight>
</syntaxhighlight>
== مصادر ==
== مصادر ==
* [https://facebook.github.io/react-native/docs/native-components-android صفحة Native UI Components في توثيق React Native الرسمي.]
* [https://facebook.github.io/react-native/docs/native-components-android صفحة Native UI Components في توثيق React Native الرسمي.]
[[تصنيف:ReactNative]]
[[تصنيف:ReactNative]]
[[تصنيف:React Native Docs]]

المراجعة الحالية بتاريخ 13:50، 9 أكتوبر 2021

هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل ScrollView و TextInput، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.

على غرار صفحة الوحدات الأصيلة، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة Android SDK. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون ImageView الموجود في مكتبة React Native الأساسية (the core React Native library).

الواجهة ImageView كمثال

في هذا المثال، سنتعرف على متطلبات الإجراء (implementation requirements) للسماح باستخدام ImageView في JavaScript.

تُنشأ الواجهات الأصيلة Native views وتُعالَج عن طريق توسيع ViewManager أو SimpleViewManager بشكل أكثر شيوعًا. يعتبر SimpleViewManager مناسبًا في هذه الحالة لأنه يطبق خاصيات شائعة مثل لون الخلفية والتعتيم وتخطيط Flexbox.

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

تزويدُ واجهة (Vending a view) أمرٌ بسيط:

  1. إنشاء الصنف الفرعي لمدير الواجهة ViewManager
  2. كتابة التابع createViewInstance
  3. توفير الوصول إلى توابع setter لضبط خاصيّات الواجهة باستخدام التوصيف ‎‎@ReactProp‎‎ (‎‎أو ‎‎@ReactPropGroup‎‎)
  4. تسجيل المدير في createViewManagers الخاص بحزمة التطبيقات.
  5. كتابة وحدة JavaScript

1. إنشاء الصنف الفرعي لمدير الواجهة ViewManager

في هذا المثال، سننشئ صنف مدير الواجهة ReactImageManager الذي يوسِّع SimpleViewManager من النوع ReactImageView. النوع ReactImageView هو نوع الكائن الذي يديره المدير، وسيكون هذا هو الواجهة الأصيلة المخصصة. يُستخدَم الاسم المعاد من طرف getName لإحالة (to reference) نوع الواجهة الأصيلة من JavaScript.

...

public class ReactImageManager extends SimpleViewManager<ReactImageView> {

  public static final String REACT_CLASS = "RCTImageView";
  ReactApplicationContext mCallerContext;

  public ReactImageManager(ReactApplicationContext reactContext) {
    mCallerContext = reactContext;
  }

  @Override
  public String getName() {
    return REACT_CLASS;
  }

2. كتابة التابع createViewInstance

تنشأ الواجهات في تابع createViewInstance، يجب أن يُهيِّئَ الواجهة نفسها في حالته الافتراضية (default state)، وستُعيَّن الخاصيات عبر استدعاء updateView لاحقًا.

  @Override
  public ReactImageView createViewInstance(ThemedReactContext context) {
    return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, mCallerContext);
  }

3. توفير الوصول إلى توابع ضابطة لضبط خاصيات الواجهة باستخدام التوصيف ‎‎@ReactProp‎‎ (‎‎أو ‎@ReactPropGroup‎‎)

يجب توفير الوصول للخاصيّات التي يجب أن تنعكس في JavaScript كتابع setter موصوف (annotated) باستخدام التوصيف ‎‎@ReactProp‎‎ (‎‎أو ‎@ReactPropGroup‎‎). يجب أن يأخذ تابع ضابط Setter الواجهة المراد تحديثها (من نوع الواجهة الحالية) كأوّل معامل، وقيمة الخاصية كمعامل ثانٍ. يجب إعلان الضابط Setter كتابع void ويجب أن يكون عامًّا (public). يُحدّد نوع الخاصية المرسلة إلى JavaScript تلقائيًا استنادًا إلى نوع المعامل القيمة الخاص بتابع setter. أنواع القيم المدعومة حاليًا هي:

‎‎

boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap‎‎

يحتاج التوصيف ‎‎@ReactProp‎‎ معاملًا إلزاميًّا واحدًا يُسمّى name من النوع String يمثّل اسم التوصيف. يُستخدَم الاسم المعيَّن للتوصيف ‎‎@ReactProp‎‎ المرتبط بالتابع الضابط setter للإشارة إلى الخاصية في جانب JavaScript.

باستثناء name، يقبل التوصيف ‎‎@ReactProp‎‎ المعاملات الاختيارية التالية: defaultBoolean، وdefaultInt، وdefaultFloat. يجب أن تكون هذه المعاملات من النوع البدائي المقابل (بالتالي boolean، وint، وfloat) وستمرَّر القيمة المعطاة إلى تابع ضابط setter في حالة إزالة الخاصية التي يشير إليها تابع ضابط setter من المكون. لاحظ أن القيم "الافتراضية ("default") موفَّرة فقط للأنواع البدائية، إذا كان التابع الضابط setter نوعًا معقدًا، ستوفَّر القيمة null كقيمة افتراضية في حالة إزالة الخاصية المطابقة.

متطلبات إعلان (declaration) تابع ضابط Setter للتوابع الموصوفة بالتوصيف ‎‎@ReactPropGroup‎‎ مختلفة عن متطلبات ‎‎@ReactProp‎‎، يرجى الرجوع إلى توثيقات صنف التوصيف ‎‎@ReactPropGroup‎‎ للمزيد من المعلومات حول هذا الموضوع.

ملاحظة مهمّة: في ReactJS تحديث قيمة الخاصية سيؤدي إلى استدعاء تابع ضباط setter. لاحظ أن إحدى الطرق لتحديث المكون هي بإزالة الخاصيات التي عُيِّنت من قبل. في هذه الحالة، سيستدعى تابع ضابط setter كذلك لإشعار مدير الواجهة بأن الخاصية قد تغيرت. في هذه الحالة، ستوفَّر القيمة "الافتراضية" (للأنواع البدائية، يُمكن تحديد القيم "الافتراضيّة" باستخدام defaultBoolean، وdefaultFloat، إلخ. والتي هي معاملات التوصيف ‎‎@ReactProp‎‎، للأنواع المعقَّدة، سيُستدعى الضابط setter مع القيمة null).

 @ReactProp(name = "src")
  public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
    view.setSource(sources);
  }

  @ReactProp(name = "borderRadius", defaultFloat = 0f)
  public void setBorderRadius(ReactImageView view, float borderRadius) {
    view.setBorderRadius(borderRadius);
  }

  @ReactProp(name = ViewProps.RESIZE_MODE)
  public void setResizeMode(ReactImageView view, @Nullable String resizeMode) {
    view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
  }

4. تسجيل مدير الواجهة ViewManager

الخطوة الأخيرة في Java هي تسجيل مدير الواجهة للتطبيق، وهذا يحدث بطريقة مشابهة للوحدات الأصيلة عبر دالة عضو حزمة (package member function) التطبيق createViewManagers.

 @Override
  public List<ViewManager> createViewManagers(
                            ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(
      new ReactImageManager(reactContext)
    );
  }

5. إنشاء وحدة JavaScript

الخطوة الأخيرة هي إنشاء وحدة JavaScript تحدد طبقة الواجهة (interface layer) بين Java و JavaScript لمستخدمي الواجهة الجديدة. يوصى بتوثيق واجهة المكون في هذه الوحدة (باستخدام Flow أو TypeScript أو التعليقات مثلًا).

// ImageView.js

import { requireNativeComponent } from 'react-native';

/**
 * Composes `View`.
 *
 * - src: string
 * - borderRadius: number
 * - resizeMode: 'cover' | 'contain' | 'stretch'
 */
module.exports = requireNativeComponent('RCTImageView');

تأخذ الدالة requireNativeComponent اسم الواجهة الأصيلة. لاحظ أنه إذا احتاج مكونك إلى القيام بأي شيء أعقد (مثل معالجة الأحداث المخصصة)، فعليك تغليف المكون الأصيل في مكون React آخر. هذا موضح في مثال MyCustomView أدناه.

الأحداث (Events)

نعرف الآن كيف نوفر الوصول لمكونات الواجهة الأصيلة التي يمكننا التحكم فيها بسهولة من JavaScript، لكن كيف نتعامل مع الأحداث من المستخدم، مثل القرص للتكبير أو للتصغير أو المسح (panning). عند حدوثِ حدثٍ أصيل، يجب أن تصدر الشيفرة الأصيلة حدثًا للواجهة في JavaScript، مع ربط الواجهتين بالقيمة المُعادة من تابع ‎‎getId()‎‎.

class MyCustomView extends View {
   ...
   public void onReceiveNativeEvent() {
      WritableMap event = Arguments.createMap();
      event.putString("message", "MyMessage");
      ReactContext reactContext = (ReactContext)getContext();
      reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
          getId(),
          "topChange",
          event);
    }
}

لربط اسم حدث topChange مع خاصية دالة رد النداء onChange في JavaScript، سجِّله عن طريق تجاوز تابع ‎‎getExportedCustomBubblingEventTypeConstants‎‎ في مدير الواجهة ViewManager الخاص بك:

public class ReactImageManager extends SimpleViewManager<MyCustomView> {
    ...
    public Map getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.builder()
            .put(
                "topChange",
                MapBuilder.of(
                    "phasedRegistrationNames",
                    MapBuilder.of("bubbled", "onChange")))
                    .build();
    }
}

تُستدعَى دالة رد النداء هذه مع الحدث الخام (raw event)، والذي نقوم بمعالجته عادةً في المكون المُغلِّف (wrapper component) لإنشاء واجهة برمجة تطبيقات أبسط:

// MyCustomView.js

class MyCustomView extends React.Component {
  constructor(props) {
    super(props);
    this._onChange = this._onChange.bind(this);
  }
  _onChange(event: Event) {
    if (!this.props.onChangeMessage) {
      return;
    }
    this.props.onChangeMessage(event.nativeEvent.message);
  }
  render() {
    return <RCTMyCustomView {...this.props} onChange={this._onChange} />;
  }
}
MyCustomView.propTypes = {
  /**
   * دالة رد نداء تُستدعى باستمرار عندما يتفاعل المستخدم مع المكوّن
   */
  onChangeMessage: PropTypes.func,
  ...
};

var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`);

مصادر