دمج واجهات React Native مع تطبيقات أصيلة قائمة

من موسوعة حسوب
مراجعة 13:36، 9 أكتوبر 2021 بواسطة جميل-بيلوني (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

إطار العمل React Native رائعٌ لبرمجة تطبيق جوال جديد من البداية. لكن، يُمكنك كذلك استخدامه لإضافة واجهة (view) واحدة أو ميّزة إلى التطبيقات الأصيلة الموجودة بالفعل. يمكنك باتّباع بضعة خطواتٍ إضافةُ ميّزات جديدة باستخدام React Native إلى تطبيقك، مثل شاشات (screens) جديدة، وأقسام واجهات، ...إلخ.

تختلف الخطوات حسب النظام الأساسي الذي تستهدفه:

الدمج مع تطبيقات Android قائمة مكتوبة بلغة Java

المفاهيم الرئيسية

أهمّ الأمور لدمج مكونات React Native في تطبيق Android الخاص بك هي:

  1. إعداد اعتماديات (dependencies) إطار العمل React Native وهيكل المجلّدات (directory structure).
  2. كتابة مكونات React Native باستخدام JavaScript.
  3. إضافة واجهة ReactRootView إلى تطبيق Android الخاص بك. ستكون هذه الواجهة بمثابة الحاوية لمكون React Native الخاص بك.
  4. بدء تشغيل خادم React Native وتشغيل التطبيق الأصيل.
  5. تحقّق من أن جانب React Native من التطبيق الخاص بك يعمل كما هو متوقع.

المتطلبات الأساسية

اتبع إرشادات بناء مشاريعٍ بشيفرة أصيلة في الدرس التقديمي لتهيئة بيئة التطوير الخاصة بك لإنشاء تطبيقات React Native لنظام التشغيل Android.

1. إعداد بنية المجلدات (directory structure)

لتسهيل العمل، أنشئ مجلدًا جديدًا لمشروع React Native المُدمَج الخاص بك، ثم انسخ مشروع Android الحالي إلى مجلد فرعي باسم ‎‎/android‎‎.

2. تثبيت اعتماديات JavaScript

انتقل إلى المجلّد الجذر (root directory) للمشروع الخاص بك وأنشئ ملف package.json جديد بالمحتويات التالية:

{
  "name": "MyReactNativeApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "yarn react-native start"
  }
}

بعد ذلك، تأكّد من تثبيت مدير الحزم Yarn.

ثبّت حزمتي ‎‎react‎‎ و‎‎react-native‎‎. افتح طرفيّةً (terminal) أو مَحثَّ أوامر (command prompt)، ثم انتقل إلى المجلّد الذي يحتوي على ملف package.json ونفّذ الأمر التالي:

$ yarn add react-native

سيؤدي هذا إلى طباعة رسالةٍ مشابهة لما يلي (مرِّر لأعلى مُخرجات yarn لمشاهدتها):

warning "react-native@0.52.2" has unmet peer dependency "react@16.2.0".

هذا يعني أنّنا بحاجة إلى تثبيت React كذلك:

$ yarn add react@version_printed_above

ضع مكان version_printed_above الإصدار المذكور في رسالة التحذير التي ظهرت لك آنفًا ضمن "react@16.2.0".

أنشأَ Yarn مجلدَ ‎‎/node_modules‎‎ جديد. يخزّن هذا المجلد جميع اعتماديات JavaScript المطلوبة لإنشاء مشروعك.

أضف ‎‎‎node_modules/‎‎ إلى ملفّ ‎‎.gitignore‎‎ الخاص بك.

إضافة React Native إلى تطبيقك

إعداد maven

أضف اعتماديّة React Native إلى ملفّ ‎‎build.gradle‎‎ الخاص بتطبيقك:

dependencies {
    implementation "com.android.support:appcompat-v7:27.1.1"
    ...
    implementation "com.facebook.react:react-native:+" // From node_modules
    implementation "org.webkit:android-jsc:+"
}

ملاحظة: إذا أردت التأكد من استعمال نسخة معينة من React Native على الدوام في بناء تطبيقك الأصيل، فاستبدل ‎‎+‎‎ بنسخة React Native التي نزّلتها من ‎‎npm‎‎.

أضف خانةً إلى مجلّد maven الخاص بإطار React Native إلى ملفّ ‎‎build.gradle‎‎. تأكد من إضافته إلى كتلة "allprojects"، فوق مستودعات maven الأخرى:

allprojects {
    repositories {
        maven {
            // All of React Native (JS, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }
        ...
    }
    ...
}

ملاحظة: تأكّد من أن المسار صحيح! لا ينبغي أن تواجهك أية أخطاء من قبيل ‎‎Failed to resolve: com.facebook.react:react-native:0.x.x‎‎ بعد تشغيل مزامنة Gradle في Android Studio.

تفعيل الربط التلقائي للوحدات الأصيلة

للاستفادة من قوة الربط التلقائي autolinking يجب أن نطبقه في عدة مواضع، أولًا أضف المدخل التالي إلى settings.gradle:

apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)

ثم أضف المدخل التالي إلى أسفل app/build.gradle:

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

إعداد الأذونات (permissions)

بعد ذلك، تأكد من تفعيل إذن الإنترنت في ملفّ AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

إذا كنت بحاجة إلى الوصول إلى نشاط DevSettingsActivity، فأضف السطر التالي إلى AndroidManifest.xml:

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

يُستخدَم هذا فقط في وضع التطوير (dev mode) عند إعادة تحميل JavaScript من خادم التطوير، لذا يمكنك إزالته في بناءات الإصدار (release builds) إذا كنت بحاجة إلى ذلك.

مرور النصوص الواضحة (مستويات الواجهة البرمجية الأعلى من 28)

بدءًا من Android 9 (مستوى API 28)، يُعطَّل مرور النصوص الواضحة (cleartext traffic) بشكل افتراضي؛ يمنع هذا تطبيقك من الاتصال بالمجمّع Metro. التغييرات أدناه تسمح بمرور النصوص الواضحة في بناءات التنقيح (debug builds).

أضف خيار ‎‎usesCleartextTraffic‎‎ إلى ‎‎Debug AndroidManifest.xml‎‎ الخاص بك:

<!-- ... -->
<application
  android:usesCleartextTraffic="true" tools:targetApi="28" >
  <!-- ... -->
</application>
<!-- ... -->

هذا غير مطلوب لبناءات الإصدار (Release builds).

لمعرفة المزيد حول ضبط أمان الشبكة (Network Security Config) وسياسة مرور النصوص الواضحة (cleartext traffic policy)، راجع هذا الرابط.

دمج الشيفرة

سنُعدّل الآن تطبيق Android الأصيل لدمج React Native معه.

مكون React Native

الجزء الأول من الشيفرة التي سنكتبها هي شيفرة React Native لشاشة "Hello World" الجديدة التي سندمجها في تطبيقنا.

1. إنشاء ملف index.js

أولاً، أنشئ ملفّ index.js فارغ في جذر مشروع React Native الخاص بك.

ملفّ index.js هو نقطة البداية لتطبيقات React Native، وهو ضروري دائمًا. يمكن أن يكون ملفًا صغيرًا يطلب (يستورد ‎‎require‎‎) ملفًا آخر يمثّل جزءًا من مكوّن أو تطبيق React Native الخاص بك، أو يمكن أن يحتوي على كامل الشيفرة البرمجية اللازمة لذلك. في حالتنا، سنضع كامل الشيفرة في ملفّ index.js.

2. أضف شيفرة React Native الخاصة بك

أنشئ المكوّن في ملفّ index.js الخاص بك. في نموذجنا هنا، سنضيف مكون ‎‎<Text>‎‎ بسيط داخل مكوّن ‎‎<View>‎ مُنسَّق:

import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

const HelloWorld = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.hello}>Hello, World</Text>
    </View>
  );
};
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center'
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10
  }
});

AppRegistry.registerComponent(
  'MyReactNativeApp',
  () => HelloWorld
);
3. ضبط الأذونات لشاشة رسائل أخطاء التطوير

إذا كان تطبيقك يستهدف واجهة Android البرمجيّة من المستوى 23 أو أكثر، فتأكد من تمكين إذن ‎‎android.permission.SYSTEM_ALERT_WINDOW‎‎ لبناء التطوير (development build). يمكنك التحقق من ذلك باستخدام ‎‎Settings.canDrawOverlays(this);‎‎. هذا مطلوب في بناءات التطوير لأنّه من الواجب عرض أخطاء تطوير React Native فوق كل النوافذ الأخرى. نظرًا لنظام الأذونات الجديد الذي قُدِّم في المستوى 23 من الواجهة البرمجيّة (Android M)، فلا بدّ للمستخدم أن يُوافق على عرض شاشة فوق جميع الشاشات الأخرى. يمكن تفعيل الإذن عن طريق إضافة الشيفرة التالية إلى النشاط (Activity) في التابع ‎‎onCreate()‎‎.

private final int OVERLAY_PERMISSION_REQ_CODE = 1;  // اختر أي قيمة
...

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                   Uri.parse("package:" + getPackageName()));
        startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
    }
}

أخيرًا، يجب إعادة تعريف (override) التابع ‎‎onActivityResult()‎‎ (كما هو موضح في الشيفرة أدناه) للتعامل مع حالتي قبول الإذن أو رفضه لتجربة مستخدم متجانسة. إضافةً إلى ذلك، لدمج الوحدات النمطية الأصيلة التي تستخدم ‎‎startActivityForResult‎‎، نحتاج إلى تمرير النتيجة إلى التابع ‎‎onActivityResult‎‎ الخاص بنسخة ‎‎ReactInstanceManager‎‎ الخاصة بنا.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                // SYSTEM_ALERT_WINDOW إذن غير معطى
            }
        }
    }
    mReactInstanceManager.onActivityResult( this, requestCode, resultCode, data );
}

الرابط العجيب: ‎‎ReactRootView‎‎

لنُضِف شيفرةً أصيلة لبدء مشغل React Native الآني (React Native runtime) ولنجعَله يُصيِّر مكوّن JavaScript الخاص بنا. للقيام بهذا، سنُنشئ نشاطًا ‎‎Activity‎‎ ليُنشئ واجهة ‎‎ReactRootView‎‎، ويبدأ تطبيق React بداخله مع ضبطه كواجهة رئيسيّة للمحتوى.

ملاحظة: إذا كنت تستهدف نسخ Android الأقدم من النسخة 5، فاستعمل صنف ‎‎AppCompatActivity‎‎ من حزمة ‎‎com.android.support:appcompat‎‎ عوضًا عن ‎‎Activity‎‎.

public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SoLoader.init(this, false);

        mReactRootView = new ReactRootView(this);
        List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
        // Packages that cannot be autolinked yet can be added manually here, for example:
        // packages.add(new MyReactNativePackage());
        // Remember to include them in `settings.gradle` and `app/build.gradle` too.

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setCurrentActivity(this)
                .setBundleAssetName("index.android.bundle")
                .setJSMainModulePath("index")
                .addPackages(packages)
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        // ‫السلسلة النصيّة  ("MyReactNativeApp")
        // يجب أن تُطابق السلسلة النصيّة الموجودة في
        // ‫AppRegistry.registerComponent()‎ في index.js
        mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
}

ملاحظة: إذا كنت تستخدم حزمةَ بدايةٍ مساعدة (starter kit) للعمل مع React Native، فاستبدل السلسلة النصيّة ‎‎"HelloWorld"‎‎ بتلك الموجودة في ملفّ ‎‎index.js‎‎ الخاص بك (وهي المعامل الأول للتابع ‎‎AppRegistry.registerComponent()‎‎).

نفذ العملية "Sync Project files with Gradle".

إذا كنت تستخدم Android Studio، فاستخدم Alt + Enter لإضافة جميع الاستيرادات المفقودة في صنف MyReactActivity. كن حريصًا على استخدام إعداد BuildConfig الخاص بحزمتك وليس الحزمة الموجودة في حزمة facebook.

ينبغي تعيين السمة Theme التي تخص MyReactActivity لتكون Theme.AppCompat.Light.NoActionBar لأن بعض مكونات واجهة مستخدم React Native تعتمد على هذه السمة.

<activity
  android:name=".MyReactActivity"
  android:label="@string/app_name"
  android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>

ملاحظة: يمكن مشاركة ReactInstanceManager من طرف عدّة أنشطة أو أجزاء (fragments) متعددة. عليك إنشاء ReactFragment أو ReactActivity خاصّة بك ويجب أن يكونَ لديك حامل مفرد (singleton holder) يحمل ReactInstanceManager. عندما تحتاج ReactInstanceManager (على سبيل المثال، لتوصيل ReactInstanceManager بدورة حياة تلك الأنشطة أو الأجزاء) فاستخدم تلك التي يوفّرها المفرد.

بعد ذلك، نحتاج إلى تمرير بعض من دوال رد النداء لدورة حياة النشاط (activity lifecycle callbacks) إلى ReactInstanceManager و ReactRootView:

@Override
protected void onPause() {
    super.onPause();

    if (mReactInstanceManager != null) {
        mReactInstanceManager.onHostPause(this);
    }
}

@Override
protected void onResume() {
    super.onResume();

    if (mReactInstanceManager != null) {
        mReactInstanceManager.onHostResume(this, this);
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();

    if (mReactInstanceManager != null) {
        mReactInstanceManager.onHostDestroy(this);
    }
    if (mReactRootView != null) {
        mReactRootView.unmountReactApplication();
    }
}

نحتاج كذلك إلى تمرير أحداث زر الرجوع إلى React Native:

@Override
 public void onBackPressed() {
    if (mReactInstanceManager != null) {
        mReactInstanceManager.onBackPressed();
    } else {
        super.onBackPressed();
    }
}

يسمح هذا للغة JavaScript بالتحكم في ما يحدث عندما يضغط المستخدم على زر رجوع الجهاز (لتنفيذ عملية التنقل [navigation] على سبيل المثال). عندما لا تُعالج JavaScript الضغط على زر الرجوع، سيُستدعى تابع ‎‎invokeDefaultOnBackPressed‎‎ الخاص بك. افتراضيًّا، يُنهي هذا نشاطَك (‎‎Activity‎‎) ببساطة.

وأخيرا، علينا ربط قائمة التطوير (dev menu). افتراضيًًا، تُفعَّل القائمة عبر هز الجهاز، ولكن هذا ليس مفيدًا في برامج المحاكاة. لذلك سنجعلها تظهر عندما تضغط على زر القائمة في الجهاز hardware menu button (استخدم Ctrl + M إذا كنت تستخدم محاكي Android Studio):

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
        mReactInstanceManager.showDevOptionsDialog();
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

نشاطك جاهز الآن لتشغيل شيفرة JavaScript.

اختبار الدمج

انتهيت الآن من جميع الخطوات الأساسية لدمج React Native مع تطبيقك الحالي. سنبدأ الآن بتشغيل مُحزّم React Native لإنشاء حزمة index.bundle والخادم الذي سيعمل على المضيف المحلي ‎‎localhost‎‎.

1. تشغيل المُحزّم

لتشغيل تطبيقك، عليك أولاً تشغيل خادم التطوير (development server) عبر تنفيذ الأمر التالي من المجلّد الجذر لمشروع React Native الخاص بك:

$ yarn start
2. تشغيل التطبيق

يمكنك الآن بناء تطبيقك كأيّ تطبيق Android آخر وتشغيله كالمعتاد.

بمجرد الوصول إلى نشاطك القائم على React داخل التطبيق، من المفترض أن تُحمَّل شيفرة JavaScript من خادم التطوير مع عرض الشاشة التالية:

إنشاء بناء إصدار (release build) في Android Studio

يمكنك استخدام Android Studio لإنشاء بناءات الإصدار أيضًا! كل ما عليك فعله هو إنشاء بناءات الإصدار من تطبيق Android الأصيل الموجود مسبقًا. هناك خطوة إضافية واحدة فقط، والتي يجب عليك القيام بها قبل كل بناء إصدار. تحتاج إلى تنفيذ ما يلي لإنشاء حزمة React Native، والتي ستُضمَّن مع تطبيق Android الأصيل الخاص بك:

$ npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/

ملاحظة: لا تنس استبدال المسارات في الأمر بالمسارات الصحيحة وإنشاء مجلد الأصول (assets folder) إذا لم يكن موجودًا.

الآن أنشئ بناء إصدارٍ لتطبيقك الأصيل من داخل Android Studio كالمعتاد وسيكون كل شيء على ما يرام!

ماذا بعد؟

يمكنك الآن متابعة تطوير تطبيقك كالمعتاد. راجع توثيق التنقيح والنشر لمعرفة المزيد حول العمل مع React Native.

الدمج مع تطبيقات iOS قائمة مكتوبة بلغة Objective-C

المفاهيم الرئيسية

أهمّ الأمور لدمج مكونات React Native في تطبيق iOS الخاص بك هي:

  1. إعداد اعتماديات (dependencies) إطار العمل React Native وهيكل المجلّدات (directory structure).
  2. فهم مكونات React Native التي ستستخدمها في تطبيقك.
  3. إضافة هذه المكوّنات كاعتماديّاتٍ باستخدام CocoaPods.
  4. كتابة مكونات React Native باستخدام JavaScript.
  5. إضافة واجهة RCTRootView إلى تطبيق iOS الخاص بك. ستكون هذه الواجهة بمثابة الحاوية لمكون React Native الخاص بك.
  6. بدء تشغيل خادم React Native وتشغيل التطبيق الأصيل.
  7. تحقّق من أن جانب React Native من التطبيق الخاص بك يعمل كما هو متوقع.

المتطلبات الأساسية

اتبع إرشادات بناء مشاريعٍ بشيفرة أصيلة في الدرس التقديمي لتهيئة بيئة التطوير الخاصة بك لإنشاء تطبيقات React Native لنظام التشغيل iOS.

1. إعداد بنية المجلدات (directory structure)

لتسهيل العمل، أنشئ مجلدًا جديدًا لمشروع React Native المُدمَج الخاص بك، ثم انسخ مشروع iOS الحالي إلى مجلد فرعي باسم ‎‎/ios‎‎.

2. تثبيت اعتماديات JavaScript

انتقل إلى المجلّد الجذر (root directory) للمشروع الخاص بك وأنشئ ملف package.json جديد بالمحتويات التالية:

{
  "name": "MyReactNativeApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "yarn react-native start"
  }
}

بعد ذلك، تأكّد من تثبيت مدير الحزم Yarn.

ثبّت حزمتي ‎‎react‎‎ و‎‎react-native‎‎. افتح طرفيّةً (terminal) أو مَحثَّ أوامر (command prompt)، ثم انتقل إلى المجلّد الذي يحتوي على ملف package.json ونفّذ الأمر التالي:

// yarn
yarn add react-native

// أو


// npm
npm install react-native

سيؤدي هذا إلى طباعة رسالةٍ مشابهة لما يلي (مرِّر لأعلى مُخرجات yarn لمشاهدتها):

warning "react-native@0.52.2" has unmet peer dependency "react@16.2.0".

هذا يعني أنّنا بحاجة إلى تثبيت React كذلك:

//yarn
yarn add react@version_printed_above

//أو
//npm
npm install react@version_printed_above

ضع مكان version_printed_above الإصدار المذكور في رسالة التحذير التي ظهرت لك آنفًا ضمن "react@16.2.0".

أنشأت عملية التثبيت الآن مجلدَ ‎‎/node_modules‎‎ جديد. يخزّن هذا المجلد جميع اعتماديات JavaScript المطلوبة لإنشاء مشروعك.

أضف ‎‎‎node_modules/‎‎ إلى ملفّ ‎‎.gitignore‎‎ الخاص بك.

3. تثبيت CocoaPods

CocoaPods هي أداةٌ لإدارة الحزم لتطوير تطبيقات iOS و macOS. سنستخدمها لإضافة شيفرة إطار React Native الفعليّة محليًا إلى مشروعك الحالي.

نوصي بتثبيت CocoaPods باستخدام Homebrew.

brew install cocoapods

ملاحظة: من الممكن تقنيًا الاستغناء عن أداة CocoaPods، ولكن هذا سيتطلب إضافة مكتباتٍ وروابط يدويًّا، ما سيُعقّد هذه العملية.

إضافة React Native إلى تطبيقك

لنفترض أن التطبيق الذي تُريد دمج مكونات جديدة معه هو لعبة 2048. إليك ما تبدو عليه القائمة الرئيسية للتطبيق الأصيل دون React Native.

أدوات سطر الأوامر لبيئة Xcode

لتثبيت أدوات سطر الأوامر (Command Line Tools). اختر "Preferences...‎" في قائمة Xcode. انتقل إلى لوحة Locations وثبّت الأدوات عن طريق تحديد أحدث إصدارٍ في قائمة Command Line Tools المنسدلة.

إعداد اعتماديات CocoaPods

قبل دمج React Native مع تطبيقك، ستحتاج إلى تحديد أجزاء إطار React Native التي تريد دمجها مع التطبيق. سنستخدم CocoaPods لتحديد أي من هذهِ المواصفات الفرعيّة (subspecs) سيعتمد عليها تطبيقك.

قائمة المواصفات الفرعية المدعومة متوفّرة في الملفّ ‎‎/node_modules/react-native/React.podspec‎‎. وتُسمَّى عمومًا حسب وظيفتها. على سبيل المثال، ستحتاج دائمًا إلى المواصفة الفرعيّة Core. التي تحتوي على AppRegistry و StyleSheet و View وغيرها من المكتبات الرئيسية في إطار React Native. إذا كنت ترغب في إضافة مكتبة Text الخاصة بإطار React Native (لاستخدام عناصر <Text> على سبيل المثال)، فستحتاج إلى المواصفة الفرعيّة RCTText. إذا كنت ترغب باستخدام مكتبة Image (لعناصر <Image> مثلًا)، فستحتاج إلى المواصفة الفرعيّة RCTImage.

يمكنك تحديد المواصفات الفرعية التي سيعتمد عليها تطبيقك في ملف Podfile. أسهل طريقة لإنشاء ملفّ Podfile هي تنفيذ الأمر ‎‎init‎‎ الخاص بأداة CocoaPods في المجلد الفرعي‎‎/ios‎‎ الخاص بمشروعك:

pod init

سوف يحتوي ملف Podfile على إعداد نموذجيّ (boilerplate setup) يُمكنك تعديله حسب طريقة الدمج التي تريدها.

ملاحظة: تختلف نسخة Podfile بحسب نسخة react-native، راجع https://react-native-community.github.io/upgrade-helper لمعرفة نسخة Podfile التي يجب استخدامها.

يجب أن يبدو ملفّ Podfile مشابهًا لهذا في النهاية:

# The target name is most likely the name of your project.
# اسم الهدف سيكون في أغلب الظن اسمَ مشروعك
target 'NumberTileGame' do

  # Your 'node_modules' directory is probably in the root of your project,
  # but if not, adjust the `:path` accordingly
  # مجلدُ الاعتماديات موجود على الأغلب في جذر مشروعك، إن لم يكن كذلك، فعدّل المسار
  pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
  pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
  pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
  pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
  pod 'React', :path => '../node_modules/react-native/'
  pod 'React-Core', :path => '../node_modules/react-native/'
  pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
  pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
  pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
  pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
  pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
  pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
  pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
  pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
  pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
  pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
  pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
  pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'

  pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
  pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
  pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
  pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
  pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
  pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
  pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

  pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
  pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
  pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

end

بعد إنشاء ملف Podfile الخاص بك، ستكون جاهزًا لتثبيت React Native.

$ pod install

سيكون الخرج مشابها لما يلي:

Analyzing dependencies
Fetching podspec for `React` from `../node_modules/react-native`
Downloading dependencies
Installing React (0.62.0)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There are 3 dependencies from the Podfile and 1 total pod installed.

إذا فشل هذا مع وجود أخطاء تحتوي على xcrun، فتأكّد من تعيين أدوات Command Line Tools في Xcode عبر قائمة Preferences ثمّ Locations.

دمج الشيفرة

سنُعدّل الآن تطبيق iOS الأصيل لدمج React Native معه. سنضيف شاشة "High Score" لتطبيق 2048 الذي بين أيدينا باستخدام React Native.

مكون React Native

الجزء الأول من الشيفرة التي سنكتبها هي شيفرة React Native لشاشة "High Score" الجديدة التي سندمجها في تطبيقنا.

1. إنشاء ملف index.js

أولاً، أنشئ ملفّ index.js فارغ في جذر مشروع React Native الخاص بك.

ملفّ index.js هو نقطة البداية لتطبيقات React Native، وهو ضروري دائمًا. يمكن أن يكون ملفًا صغيرًا يطلب (يستورد ‎‎require‎‎) ملفًا آخر يمثّل جزءًا من مكوّن أو تطبيق React Native الخاص بك، أو يمكن أن يحتوي على كامل الشيفرة البرمجية اللازمة لذلك. في حالتنا، سنضع كامل الشيفرة في ملفّ index.js.

2. أضف شيفرة React Native الخاصة بك

أنشئ المكوّن في ملفّ index.js الخاص بك. في نموذجنا هنا، سنضيف مكون ‎‎<Text>‎‎ بسيط داخل مكوّن ‎‎<View>‎‎ مُنسَّق:

import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

const RNHighScores = ({ scores }) => {
  const contents = scores.map((score) => (
    <Text key={score.name}>
      {score.name}:{score.value}
      {'\n'}
    </Text>
  ));
  return (
    <View style={styles.container}>
      <Text style={styles.highScoresTitle}>
        2048 High Scores!
      </Text>
      <Text style={styles.scores}>{contents}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF'
  },
  highScoresTitle: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10
  },
  scores: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5
  }
});

// Module name
AppRegistry.registerComponent('RNHighScores', () => RNHighScores);

RNHighScores هو اسم وحدتك الذي سيُستخدَم عند إضافة واجهة إلى React Native من داخل تطبيق iOS الخاص بك.

الرابط العجيب: ‎‎RCTRootView‎‎

الآن، وبعد إنشاء مكون React Native الخاص بك عبر ملفّ index.js، تحتاج إلى إضافة هذا المكون إلى مُتحكّم ViewController جديد أو موجود مسبقا. أيسر سبيل إلى ذلك هو إنشاء مسار حدث (event path) اختياريًّا يُشير إلى المكون الخاص بك، ثم إضافة هذا المكون إلى ViewController موجود.

سنربط مكوّن React Native الخاص بنا مع واجهة أصيلة جديد في متحكّم ViewController، والذي سيستضيف المكوّن. الواجهة الأصيلة تسمّى RCTRootView.

1. إنشاء مسار حدث (Event Path)

يمكنك إضافة رابط جديد في قائمة اللعبة الرئيسية للانتقال إلى صفحة "High Score" المبنيّة بإطار React Native.

2. معالج الأحداث

سنضيف الآن معالج أحداثٍ من رابط القائمة. سيُضاف تابع إلى متحكّم ViewController الرئيسي للتطبيق الخاص بك. هنا سنعتمد على واجهة RCTRootView.

عند إنشاء تطبيق React Native، يُستخدَم المُجمّع Metro لإنشاء ملفّ ‎‎index.bundle‎‎ الذي سيُقدَّم من طرف خادم React Native. سيحتوي ملفّ index.bundle على وحدة RNHighScore الخاصة بنا. لذا، سنحتاج إلى توجيه واجهة RCTRootView الخاص بنا إلى مكان الملفّ index.bundle (عبر NSURL) وربطه بالوحدة.

لتسهيل التنقيح (debugging)، سنُسجّل (log) أنّ معالج الأحداث قد استُدعَى. بعد ذلك، سننشئ سلسلة نصيّة تحتوي على مكان شيفرة React Native الموجودة داخل ملفّ index.bundle. أخيرًا، سننشئ الواجهة RCTRootView الرئيسيّة. لاحظ كيف نُشير إلى الاسم RNHighScores على أنّه اسم الوحدة (moduleName) الذي أنشأناه أعلاه عند كتابة شيفرة مكوّن React Native الخاص بنا.

أولاً استورد ترويسة RCTRootView:

#import <React/RCTRootView.h>

الخاصيّات الأوليّة‎‎initialProperties‎ ‎ موجودة هنا لأغراضٍ توضيحيّة ولتكون لدينا بعض البيانات لعرضها في شاشة "High Score". في مكوّن React Native الخاص بنا، سنستخدم this.props للوصول إلى هذه البيانات.

- (IBAction)highScoreButtonPressed:(id)sender {
    NSLog(@"High Score Button Pressed");
    NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];

    RCTRootView *rootView =
      [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                  moduleName: @"RNHighScores"
                           initialProperties:
                             @{
                               @"scores" : @[
                                 @{
                                   @"name" : @"Alex",
                                   @"value": @"42"
                                  },
                                 @{
                                   @"name" : @"Joel",
                                   @"value": @"10"
                                 }
                               ]
                             }
                               launchOptions: nil];
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view = rootView;
    [self presentViewController:vc animated:YES completion:nil];
}

لاحظ أن المقطع ‎‎RCTRootView initWithURL‎‎ يبدأ بتشغيل آلة افتراضية JSC جديدة (JSC VM). لتوفير الموارد وتبسيط الاتصال بين واجهات React Native في أجزاء مختلفة من التطبيق الأصلي، يُمكنك إنشاء عدّة واجهات مدعومة من React Native مرتبطة بوقت تشغيل JS واحد. للقيام بهذا، استخدم RCTBridge initWithBundleURL بدلاً من استخدام‎‎[RCTRootView alloc] initWithURL ‎‎ لإنشاء جسر ثم استخدم ‎‎RCTRootView initWithBridge‎‎.

عند نقل تطبيقك إلى بيئة الإنتاج (production)، يمكن لعنوان NSURL الإشارة إلى ملفّ مُحزَّم مسبقًا (pre-bundled) على القرص عبر سطرٍ برمجيّ مثل:

[[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

يمكنك استخدام برمجيّة react-native-xcode.sh الموجودة في المسار ‎‎node_modules/react-native/scripts/‎‎ لإنشاء هذا الملف المُحزّم مسبقًا.

3. الربط

اربط الرابط الجديد في القائمة الرئيسية بتابعِ معالجِ الأحداث المضاف حديثًا.

من أسهل الطرق للقيام بهذا هي فتح الواجهة في لوحة العمل (storyboard) والنقر بزر الفأرة الأيمن على الرابط الجديد. حدّد حدثًا مثل حدث Touch Up Inside، واسحبه إلى لوحة العمل ثم حدّد الواجهة المُنشَأ من القائمة المتوفرة.

اختبار الدمج

انتهيت الآن من جميع الخطوات الأساسية لدمج React Native مع تطبيقك الحالي. سنبدأ الآن بتشغيل المُجمّع Metro لإنشاء حزمة index.bundle والخادم الذي سيعمل على المضيف المحلي ‎‎localhost‎‎.

1. إضافة استثناء App Transport Security

حظرت شركة Apple تحميل موارد نصوص HTTP الضمنيّة (implicit cleartext HTTP). لذلك نحتاج إلى إضافة ما يلي إلى ملف Info.plist الخاص بالتطبيق (أو ما يعادله).

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

سياسة أمان App Transport Security مفيدة لمستخدمي التطبيق. تأكّد من إعادة تمكينها قبل إطلاق تطبيقك للإنتاج.

2. تشغيل المُحزّم

لتشغيل تطبيقك، عليك أولاً تشغيل خادم التطوير (development server) عبر تنفيذ الأمر التالي من المجلّد الجذر لمشروع React Native الخاص بك:

//npm
npm start

//yarn
yarn start
3. تشغيل التطبيق

إذا كنت تستخدم Xcode أو محرّرك المفضّل، فأنشئ تطبيق iOS أصيل وشغّله كالمعتاد. أو يمكنك تشغيل التطبيق من سطر الأوامر عبر تنفيذ الأمر التالي من المجلّد الجذر لمشروعك :

$ npx react-native run-ios

سترى الآن في تطبيقنا، رابطًا يشير إلى شاشة "High Scores"، وعند النقر فوقه، سترى مكوّن React Native الخاص بك.

هذه هي الشاشة الرئيسيّة للتطبيق الأصيل:

هذه شاشة مكوّن React Native:

إذا واجهتك مشكلة في تقرير الوحدات (module resolution) عند تشغيل تطبيقك، فيرجى الاطلاع على هذه الصفحة للحصول على معلوماتٍ قد تساعدك على حل مشكلتك. يبدو هذا التعليق حلًّا ممكنًا.

ماذا بعد؟

يمكنك الآن متابعة تطوير تطبيقك كالمعتاد. راجع توثيق التنقيح والنشر لمعرفة المزيد حول العمل مع React Native.

الدمج مع تطبيقات iOS قائمة مكتوبة بلغة Swift

المفاهيم الرئيسية

أهمّ الأمور لدمج مكونات React Native في تطبيق iOS الخاص بك هي:

  1. إعداد اعتماديات (dependencies) إطار العمل React Native وهيكل المجلّدات (directory structure).
  2. فهم مكونات React Native التي ستستخدمها في تطبيقك.
  3. إضافة هذه المكوّنات كاعتماديّاتٍ باستخدام CocoaPods.
  4. كتابة مكونات React Native باستخدام JavaScript.
  5. إضافة الواجهة RCTRootView إلى تطبيق iOS الخاص بك. ستكون هذه الواجهة بمثابة الحاوية لمكون React Native الخاص بك.
  6. بدء تشغيل خادم React Native وتشغيل التطبيق الأصيل.
  7. تحقّق من أن جانب React Native من التطبيق الخاص بك يعمل كما هو متوقع.

المتطلبات الأساسية

اتبع إرشادات بناء مشاريعٍ بشيفرة أصيلة في الدرس التقديمي لتهيئة بيئة التطوير الخاصة بك لإنشاء تطبيقات React Native لنظام التشغيل iOS.

1. إعداد بنية المجلدات (directory structure)

لتسهيل العمل، أنشئ مجلدًا جديدًا لمشروع React Native المُدمَج الخاص بك، ثم انسخ مشروع iOS الحالي إلى مجلد فرعي باسم ‎‎/ios‎‎.

2. تثبيت اعتماديات JavaScript

انتقل إلى المجلّد الجذر (root directory) للمشروع الخاص بك وأنشئ ملف package.json جديد بالمحتويات التالية:

{
  "name": "MyReactNativeApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "yarn react-native start"
  }
}

بعد ذلك، تأكّد من تثبيت مدير الحزم Yarn.

ثبّت حزمتي ‎‎react‎‎ و‎‎react-native‎‎. افتح طرفيّةً (terminal) أو مَحثَّ أوامر (command prompt)، ثم انتقل إلى المجلّد الذي يحتوي على ملف package.json ونفّذ الأمر التالي:

$ yarn add react-native

سيؤدي هذا إلى طباعة رسالةٍ مشابهة لما يلي (مرِّر لأعلى مُخرجات yarn لمشاهدتها):

warning "react-native@0.52.2" has unmet peer dependency "react@16.2.0".

هذا يعني أنّنا بحاجة إلى تثبيت React كذلك:

$ yarn add react@version_printed_above

ضع مكان version_printed_above الإصدار المذكور في رسالة التحذير التي ظهرت لك آنفًا ضمن "react@16.2.0".

أنشأَ Yarn مجلدَ ‎‎/node_modules‎‎ جديد. يخزّن هذا المجلد جميع اعتماديات JavaScript المطلوبة لإنشاء مشروعك.

أضف ‎‎‎node_modules/‎‎ إلى ملفّ ‎‎.gitignore‎‎ الخاص بك.

3. تثبيت CocoaPods

CocoaPods هي أداةٌ لإدارة الحزم لتطوير تطبيقات iOS و macOS. سنستخدمها لإضافة شيفرة إطار React Native الفعليّة محليًا إلى مشروعك الحالي.

نوصي بتثبيت CocoaPods باستخدام Homebrew.

$ brew install cocoapods

ملاحظة: من الممكن تقنيًا الاستغناء عن أداة CocoaPods، ولكن هذا سيتطلب إضافة مكتباتٍ وروابط يدويًّا، ما سيُعقّد هذه العملية.

إضافة React Native إلى تطبيقك

لنفترض أن التطبيق الذي تُريد دمج مكونات جديدة معه هو لعبة 2048. إليك ما تبدو عليه القائمة الرئيسية للتطبيق الأصيل دون React Native.

أدوات سطر الأوامر لبيئة Xcode

لتثبيت أدوات سطر الأوامر (Command Line Tools). اختر "Preferences...‎" في قائمة Xcode. انتقل إلى لوحة Locations وثبّت الأدوات عن طريق تحديد أحدث إصدارٍ في قائمة Command Line Tools المنسدلة.

إعداد اعتماديات CocoaPods

قبل دمج React Native مع تطبيقك، ستحتاج إلى تحديد أجزاء إطار React Native التي تريد دمجها مع التطبيق. سنستخدم CocoaPods لتحديد أي من هذهِ المواصفات الفرعيّة (subspecs) سيعتمد عليها تطبيقك.

قائمة المواصفات الفرعية المدعومة متوفّرة في الملفّ ‎‎/node_modules/react-native/React.podspec‎‎. وتُسمَّى عمومًا حسب وظيفتها. على سبيل المثال، ستحتاج دائمًا إلى المواصفة الفرعيّة Core. التي تحتوي على AppRegistry و StyleSheet و View وغيرها من المكتبات الرئيسية في إطار React Native. إذا كنت ترغب في إضافة مكتبة Text الخاصة بإطار React Native (لاستخدام عناصر <Text> على سبيل المثال)، فستحتاج إلى المواصفة الفرعيّة RCTText. إذا كنت ترغب باستخدام مكتبة Image (لعناصر <Image> مثلًا)، فستحتاج إلى المواصفة الفرعيّة RCTImage.

يمكنك تحديد المواصفات الفرعية التي سيعتمد عليها تطبيقك في ملف Podfile. أسهل طريقة لإنشاء ملفّ Podfile هي تنفيذ الأمر ‎‎init‎‎ الخاص بأداة CocoaPods في المجلد الفرعي‎‎/ios‎‎ الخاص بمشروعك:

$ pod init

سوف يحتوي ملف Podfile على إعداد نموذجيّ (boilerplate setup) يُمكنك تعديله حسب طريقة الدمج التي تريدها.

ملاحظة: تختلف نسخة Podfile بحسب نسخة react-native، راجع https://react-native-community.github.io/upgrade-helper لمعرفة نسخة Podfile التي يجب استخدامها.

يجب أن يبدو ملفّ Podfile مشابهًا لهذا في النهاية:

source 'https://github.com/CocoaPods/Specs.git'

# مطلوب للتطبيقات المكتوبة بلغة
# Swift

platform :ios, '8.0'
use_frameworks!

# The target name is most likely the name of your project.
# اسم الهدف سيكون في أغلب الظن اسمَ مشروعك
target 'swift-2048' do

  # Your 'node_modules' directory is probably in the root of your project,
  # but if not, adjust the `:path` accordingly
  # مجلدُ الاعتماديات موجود على الأغلب في جذر مشروعك، إن لم يكن كذلك، فعدّل المسار
  pod 'React', :path => '../node_modules/react-native', :subspecs => [
    'Core',
    'CxxBridge', # Include this for RN >= 0.47
    'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
    'RCTText',
    'RCTNetwork',
    'RCTWebSocket', # needed for debugging
    # Add any other subspecs you want to use in your project
    # أضف أي مواصفات فرعية أخرى حسب ما تريد الاعتماد عليه في مشروعك
  ]
  # Explicitly include Yoga if you are using RN >= 0.42.0
  pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga"

  # Third party deps podspec link
  pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
  pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
  pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

end

بعد إنشاء ملف Podfile الخاص بك، ستكون جاهزًا لتثبيت React Native.

$ pod install

سيكون الخرج مشابها لما يلي:

Analyzing dependencies
Fetching podspec for `React` from `../node_modules/react-native`
Downloading dependencies
Installing React (0.62.0)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There are 3 dependencies from the Podfile and 1 total pod installed.

إذا فشل هذا مع وجود أخطاء تحتوي على xcrun، فتأكّد من تعيين أدوات Command Line Tools في Xcode عبر قائمة Preferences ثمّ Locations.

إذا حصلت على تنبيه مشابه للنصّ التالي:

‎‎"The swift-2048 [Debug] target overrides the FRAMEWORK_SEARCH_PATHS build setting defined in Pods/Target Support Files/Pods-swift-2048/Pods-swift-2048.debug.xcconfig. This can lead to problems with the CocoaPods installation"‎‎

فتأكّد من أن مسارات البحث عن أطر العمل ‎‎Framework Search Paths‎‎ في إعدادات البناء ‎‎Build Settings‎‎ لكل من ‎‎Debug‎‎ و‎‎Release‎‎ تحتوي فقط على ‎‎$(inherited)‎‎.

دمج الشيفرة

سنُعدّل الآن تطبيق iOS الأصيل لدمج React Native معه. سنضيف شاشة "High Score" لتطبيق 2048 الذي بين أيدينا باستخدام React Native.

مكون React Native

الجزء الأول من الشيفرة التي سنكتبها هي شيفرة React Native لشاشة "High Score" الجديدة التي سندمجها في تطبيقنا.

1. إنشاء ملف index.js

أولاً، أنشئ ملفّ index.js فارغ في جذر مشروع React Native الخاص بك.

ملفّ index.js هو نقطة البداية لتطبيقات React Native، وهو ضروري دائمًا. يمكن أن يكون ملفًا صغيرًا يطلب (‎‎require‎‎) ملفًا آخر يمثّل جزءًا من مكوّن أو تطبيق React Native الخاص بك، أو يمكن أن يحتوي على كامل الشيفرة البرمجية اللازمة لذلك. في حالتنا، سنضع كامل الشيفرة في ملفّ index.js.

2. أضف شيفرة React Native الخاصة بك

أنشئ المكوّن في ملفّ index.js الخاص بك. في نموذجنا هنا، سنضيف مكون ‎‎<Text>‎‎ بسيط داخل مكوّن ‎‎<View>‎‎ مُنسَّق:

import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

const RNHighScores = ({ scores }) => {
  const contents = scores.map((score) => (
    <Text key={score.name}>
      {score.name}:{score.value}
      {'\n'}
    </Text>
  ));
  return (
    <View style={styles.container}>
      <Text style={styles.highScoresTitle}>
        2048 High Scores!
      </Text>
      <Text style={styles.scores}>{contents}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF'
  },
  highScoresTitle: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10
  },
  scores: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5
  }
});

// Module name
AppRegistry.registerComponent('RNHighScores', () => RNHighScores);

RNHighScores هو اسم وحدتك الذي سيُستخدَم عند إضافة واجهة إلى React Native من داخل تطبيق iOS الخاص بك.

الرابط العجيب: ‎‎RCTRootView‎‎

الآن، وبعد إنشاء مكون React Native الخاص بك عبر ملفّ index.js، تحتاج إلى إضافة هذا المكون إلى مُتحكّم ViewController جديد أو موجود مسبقا. أيسر سبيل إلى ذلك هو إنشاء مسار حدث (event path) اختياريًّا يُشير إلى المكون الخاص بك، ثم إضافة هذا المكون إلى ViewController موجود.

سنربط مكوّن React Native الخاص بنا مع واجهة أصيلة جديد في متحكّم ViewController، والذي سيستضيف المكوّن. الواجهة الأصيلة تسمّى RCTRootView.

1. إنشاء مسار حدث (Event Path)

يمكنك إضافة رابط جديد في قائمة اللعبة الرئيسية للانتقال إلى صفحة "High Score" المبنيّة بإطار React Native.

2. معالج الأحداث

سنضيف الآن معالج أحداثٍ من رابط القائمة. سيُضاف تابع إلى متحكّم ViewController الرئيسي للتطبيق الخاص بك. هنا سنعتمد على واجهة RCTRootView.

عند إنشاء تطبيق React Native، يُستخدَم المجمّع Metro لإنشاء ملفّ ‎‎index.bundle‎‎ الذي سيُقدَّم من طرف خادم React Native. سيحتوي ملفّ index.bundle على وحدة RNHighScore الخاصة بنا. لذا، سنحتاج إلى توجيه الواجهة ‎‎RCTRootView‎‎ الخاص بنا إلى مكان الملفّ index.bundle (عبر NSURL) وربطه بالوحدة.

لتسهيل التنقيح (debugging)، سنُسجّل (log) أنّ معالج الأحداث قد استُدعَى. بعد ذلك، سننشئ سلسلة نصيّة تحتوي على مكان شيفرة React Native الموجودة داخل ملفّ index.bundle. أخيرًا، سننشئ الواجهة RCTRootView الرئيسيّ. لاحظ كيف نُشير إلى الاسم RNHighScores على أنّه اسم الوحدة moduleName الذي أنشأناه أعلاه عند كتابة شيفرة مكوّن React Native الخاص بنا.

أولاً استورد (عبر ‎‎import‎‎) مكتبة React:

import React

الخاصيّات الأوليّة‎‎initialProperties‎ ‎ موجودة هنا لأغراضٍ توضيحيّة، ولتكون لدينا بعض البيانات لعرضها في شاشة "High Score". في مكوّن React Native الخاص بنا، سنستخدم this.props للوصول إلى هذه البيانات.

@IBAction func highScoreButtonTapped(sender : UIButton) {
  NSLog("Hello")
  let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")
  let mockData:NSDictionary = ["scores":
      [
          ["name":"Alex", "value":"42"],
          ["name":"Joel", "value":"10"]
      ]
  ]

  let rootView = RCTRootView(
      bundleURL: jsCodeLocation,
      moduleName: "RNHighScores",
      initialProperties: mockData as [NSObject : AnyObject],
      launchOptions: nil
  )
  let vc = UIViewController()
  vc.view = rootView
  self.present(vc, animated: true, completion: nil)
}

لاحظ أن المقطع ‎‎RCTRootView initWithURL‎‎ يبدأ بتشغيل آلة افتراضية JSC جديدة (JSC VM). لتوفير الموارد وتبسيط الاتصال بين واجهات React Native في أجزاء مختلفة من التطبيق الأصلي، يُمكنك إنشاء عدّة واجهات مدعومة من React Native مرتبطة بوقت تشغيل JS واحد. للقيام بهذا، استخدم RCTBridge initWithBundleURL بدلاً من ستخدام ‎‎RCTRootView bundleURL ‎‎ لإنشاء جسر ثم استخدم ‎‎RCTRootView initWithBridge‎‎.

عند نقل تطبيقك إلى بيئة الإنتاج (production)، يمكن لعنوان NSURL الإشارة إلى ملفّ مُحزَّم مسبقًا (pre-bundled) على القرص عبر سطرٍ برمجيّ مثل:

‎‎let mainBundle = NSBundle(URLForResource: "main" withExtension:"jsbundle")‎‎

يمكنك استخدام برمجيّة react-native-xcode.sh الموجودة في المسار ‎‎node_modules/react-native/scripts/‎‎ لإنشاء هذا الملف المُحزّم مسبقًا.

3. الربط

اربط الرابط الجديد في القائمة الرئيسية بتابعِ معالجِ الأحداث المضاف حديثًا.

من أسهل الطرق للقيام بهذا هي فتح الواجهة في لوحة العمل (storyboard) والنقر بزر الفأرة الأيمن على الرابط الجديد. حدّد حدثًا مثل حدث ‎‎Touch Up Inside‎‎، واسحبه إلى لوحة العمل ثم حدّد الواجهة المُنشَأة من القائمة المتوفرة.

اختبار الدمج

انتهيت الآن من جميع الخطوات الأساسية لدمج React Native مع تطبيقك الحالي. سنبدأ الآن بتشغيل مُحزّم React Native لإنشاء حزمة index.bundle والخادم الذي سيعمل على المضيف المحلي ‎‎localhost‎‎.

1. إضافة استثناء App Transport Security

حظرت شركة Apple تحميل موارد نصوص HTTP الضمنيّة (implicit cleartext HTTP). لذلك نحتاج إلى إضافة ما يلي إلى ملف Info.plist الخاص بالتطبيق (أو ما يعادله).

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

سياسة أمان App Transport Security مفيدة لمستخدمي التطبيق. تأكّد من إعادة تمكينها قبل إطلاق تطبيقك للإنتاج.

2. تشغيل المُحزّم

لتشغيل تطبيقك، عليك أولاً تشغيل خادم التطوير (development server) عبر تنفيذ الأمر التالي من المجلّد الجذر لمشروع React Native الخاص بك:

$ npm start
3. تشغيل التطبيق

إذا كنت تستخدم Xcode أو محرّرك المفضّل، فأنشئ تطبيق iOS أصيل وشغّله كالمعتاد. أو يمكنك تشغيل التطبيق من سطر الأوامر عبر تنفيذ الأمر التالي من المجلّد الجذر لمشروعك:

$ npx react-native run-ios

سترى الآن في تطبيقنا، رابطًا يشير إلى شاشة "High Scores"، وعند النقر فوقه، سترى مكوّن React Native الخاص بك.

هذه هي الشاشة الرئيسيّة للتطبيق الأصيل:

هذه شاشة مكوّن React Native:

إذا واجهتك مشكلة في تقرير الوحدات (module resolution) عند تشغيل تطبيقك، فيرجى الاطلاع على هذه الصفحة للحصول على معلوماتٍ قد تساعدك على حل مشكلتك. يبدو هذا التعليق حلًّا ممكنًا.

ماذا بعد؟

يمكنك الآن متابعة تطوير تطبيقك كالمعتاد. راجع توثيق التنقيح والنشر لمعرفة المزيد حول العمل مع React Native.


مصادر