مكونات واجهة المستخدم الأصيلة (Android) في React Native
هناك الكثير من أدوات واجهة المستخدم (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.
تُنشأ العروض الأصيلة وتُعالَج عن طريق توسيع ViewManager
أو SimpleViewManager
بشكل أكثر شيوعًا. يعتبر SimpleViewManager
مناسبًا في هذه الحالة لأنه يطبق خاصيات شائعة مثل لون الخلفية والتعتيم وتخطيط Flexbox.
هذه الأصناف الفرعية هي مفردات (singletons) أساسا، أي تُنشأ نسخة واحدة فقط لكل منها بواسطة الجسر. وهي تُزوِّد العروضَ الأصيلة إلى NativeViewHierarchyManager
، والذي يُفوِّض لها مرة أخرى لتعيين وتحديث خاصيات العروض حسب الضرورة. عادةً ما يكون مدراء العروض (أصناف ViewManager
) مفوَّضين للعروض، مُرسلةً الأحداث مرة أخرى إلى JavaScript عبر الجسر.
تزويدُ عرضِِ (Vending a view) أمرٌ بسيط:
- إنشاء الصنف الفرعي لمدير العرض ViewManager
- كتابة التابع
createViewInstance
- توفير الوصول إلى توابع setter لضبط خاصيّات العرض باستخدام التوصيف
@ReactProp
(أو @ReactPropGroup
) - تسجيل المدير في
createViewManagers
الخاص بحزمة التطبيقات. - كتابة وحدة 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. توفير الوصول إلى توابع setter لضبط خاصيّات العرض باستخدام التوصيف @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`);