[譯] React Native 與 iOS 和 Android 通訊

掘金翻譯計劃發表於2019-02-20

React Native 流行的最大原因之一是我們可以在 Native 語言和 JavaScript 程式碼之間建立橋樑。這意味著我們可以複用在 iOS 和 Android 中建立的所有可重用庫。

要建立一個商業級的應用程式,您需要使用 Native Bridge。React Native 可以同時在 iOS 和 Android 上執行,但關於它如何跨平臺的文章教程非常少。在本文中,我們將建立一個 Native Bridge,以便從 JavaScript 訪問 Swift 和 Java 類。

文字是此係列的第一部分,第二部分可以在 Native Bridge of UI component 中找到 這裡

程式碼可以在這裡找到 -> github.com/nalwayaabhi…

建立一個 LightApp(手電筒)

為了更好地理解 Native Module,我們將使用 react-native CLI 建立一個簡單的 LightApp 示例。

$ react-native init LightApp
$ cd LightApp
複製程式碼

接下來,我們將在 Swift 和 Java 中建立一個 Bulb 類,稍後將在 React 元件中使用它。這是一個跨平臺的示例,相同的 React 程式碼將同時適用於iOS和Android。

現在我們已經建立了專案的基本框架,接下來我們將本文分為兩部分:

第一節 — 與原生 iOS 通訊

第二節 — 與原生 Android 通訊

第一節 — 與原生 iOS 通訊

在本節中,我們將重點關注 iOS,瞭解如何在 Swift/Objective C 和 React 元件間建立橋樑。有以下三個步驟:

步驟 1) 建立一個 Bulb 類 並且完整初步通訊

步驟 2) 理解 GCD Queue 並且解決出現的警告

步驟 3) 從 Swift 和 Callbacks 訪問 JavaScript 中的變數

步驟 1) 建立一個 Bulb 類 並且完成初步通訊

首先,我們將在 swift 中建立一個 Bulb 類,它將具有一個靜態類變數 isOn 和一些其他函式。然後我們將從 Javascript 訪問這個 swift 類。讓我們首先在 ios 資料夾中開啟 LightApp.xcodeproj 檔案。此時 Xcode 應該會被開啟。

在 Xcode 中開啟專案後,建立一個新的 Swift檔案 Bulb.swift,如下所示:

[譯] React Native 與 iOS 和 Android 通訊

我們還要點選 Create Bridging Header,建立一個檔案 LightApp-Bridging-Header.h 它將有助於 Swift 和 Objective C 程式碼之間的通訊。請記住,在專案中,我們只有一個 Bridge Header 檔案。因此,如果我們新增新檔案,我們可以重用此檔案。

將以下程式碼加入 -Bridging-Header.h 檔案:

#import "React/RCTBridgeModule.h"
複製程式碼

RCTBridgeModul 將提供一個介面,用於註冊 Bridge 模組。

接下來將以下程式碼輸入 Bulb.swift

import Foundation

@objc(Bulb)
class Bulb: NSObject {

    @objc
    static var isOn = false

    @objc
    func turnOn() {
        Bulb.isOn = true
        print("Bulb is now ON")
    }
}
複製程式碼

我們建立了 Bulb 類,它繼承自 NSObject。大多數 Objective-C 類的根類是 NSObject,子類從該類繼承執行時系統的基本介面,因此它們有與 Objective-C 物件相同的能力。我們在函式和類之前使用了 @objc,這將使那個類,方法或物件可用於 Objective C。

@objc 註解使您的 Swift API 在 Objective-C 和 Objective-C 執行時可用。

現在選擇 File -> New -> File 建立一個新檔案,然後選擇 Objective-C 檔案,然後將該檔案命名為 Bulb.m 並新增以下程式碼:

#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(Bulb, NSObject)
RCT_EXTERN_METHOD(turnOn)
@end
複製程式碼

除非顯式地指定,否則 React Native 不會將任何 Bulb 中的函式暴露給 React JavaScript。為此,我們使用了 RCT_EXPORT_METHOD() 巨集。所以我們已經暴露了 Bulb 類和 turnOn 函式給了我們的 Javascript 程式碼。由於 Swift 物件被轉換為了 Javascript 物件,因此其中一定存在一種對應關係。RCT_EXPORT_METHOD 支援所有標準 JSON 物件型別:

  • NSString 對應 string
  • NSInteger、float、double、CGFloat、NSNumber 對應 number
  • BOOL 對應 boolean
  • NSArray 對應 array
  • NSDictionary 對應 包含此列表中的字串鍵和任何型別的值的物件
  • RCTResponseSenderBlock 對應 function

現在讓我們更新 JavaScript 程式碼並從我們的 React 元件訪問這個 Bulb 類。為此,請開啟 App.js 並更新為以下程式碼:

import React, {Component} from 'react';
import {StyleSheet, Text, View, NativeModules, Button} from 'react-native';

export default class App extends Component{
  turnOn = () => {
    NativeModules.Bulb.turnOn();
  }
  render() {
  return (
    <View style={styles.container}>
    <Text style={styles.welcome}>Welcome to Light App!!</Text>
    <Button
        onPress={this.turnOn}
    title="Turn ON "
    color="#FF6347" />
    </View>
  );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});
複製程式碼

現在執行iOS模擬器:

[譯] React Native 與 iOS 和 Android 通訊

現在開啟 Xcode 控制檯檢視日誌,我們可以看到從 JavaScript 程式碼呼叫 Swift turnOn 方法。(因為我們已經看到了方法中執行的日誌)

[譯] React Native 與 iOS 和 Android 通訊

步驟 2) 理解 GCD Queue並且解決出現的警告

現在讓我們修復模擬器底部和瀏覽器控制檯中顯示的警告:

Bulb 模組需要主佇列設定,因為它覆蓋 init但 沒有實現 requiresMainQueueSetup。在以後的版本中,React Native 將預設初始化後臺執行緒上的所有原生模組,除非明確選擇不需要。

為了更好地理解,讓我們瞭解 React Native 執行的所有執行緒:

  • Main thread:UI 渲染執行的執行緒
  • Shadow queue:佈局發生的地方
  • JavaScript thread:JS 程式碼實際執行的地方

除非另有說明,否則每個原生模組都有自己的 GCD 佇列。 現在,由於這個原生模組將在不同的執行緒上執行,並且我們的主執行緒依賴於它,它會顯示此警告。 要使此程式碼在 MainQueue 上執行,請開啟 Bulb.swift 並新增此函式。

@objc
static func requiresMainQueueSetup() -> Bool {
  return true
}
複製程式碼

您可以明確提及 return false 以讓它在單獨的執行緒中執行。

步驟 3) 從Swift和Callbacks訪問JavaScript中的變數

現在讓我們將 Bulb 的開關(ON 或 OFF)值新增到 React 螢幕。為此,我們將 getStatus 函式新增到 Bulb.swift 並從 JavaScript 程式碼呼叫該方法。 我們將建立此方法作為回撥。

React Native 橋是非同步的,因此將結果傳遞給 JavaScript 的唯一方法是使用回撥或觸發事件

讓我們用粗體更新 Bulb.swift 中的程式碼:

@objc(Bulb)
class Bulb: NSObject {
    @objc
    static var isOn = false

    @objc
    func turnOn() {
        Bulb.isOn = true
        print("Bulb is now ON")
    }

    @objc
    func turnOff() {
        Bulb.isOn = false
        print("Bulb is now OFF")
    }

    @objc
    func getStatus(_ callback: RCTResponseSenderBlock) {
        callback([NSNull(), Bulb.isOn])
    }

    @objc
        static func requiresMainQueueSetup() -> Bool {
        return true
    }
}
複製程式碼

getStatus() 方法接收一個我們將從您的 JavaScript 程式碼傳遞的回撥引數。我們用值陣列呼叫了回撥函式,這些函式將暴露在 JavaScript 中。我們已經將 NSNull() 作為第一個元素傳遞,我們將其視為回撥中的錯誤。

我們需要將這個 Swift 方法暴露給 JavaScript,所以新增下方的粗體行程式碼到 Bulb.m 中:

@interface RCT_EXTERN_MODULE(Bulb, NSObject)
RCT_EXTERN_METHOD(turnOn)
RCT_EXTERN_METHOD(turnOff)
RCT_EXTERN_METHOD(getStatus: (RCTResponseSenderBlock)callback)
@end
複製程式碼

我們已將 (RCTResponseSenderBlock)callback 暴露為函式 getStatus 的引數

然後最後更新React程式碼:

import React, {Component} from 'react';
import {StyleSheet, Text, View, NativeModules, Button} from 'react-native';

export default class App extends Component{
    constructor(props) {
      super(props);
      this.state = { isOn: false };
      this.updateStatus();
    }

    turnOn = () => {
      NativeModules.Bulb.turnOn();
      this.updateStatus()
    }

    turnOff = () => {
      NativeModules.Bulb.turnOff();
      this.updateStatus()
    }

    updateStatus = () => {
      NativeModules.Bulb.getStatus( (error, isOn)=>{
      this.setState({ isOn: isOn});
    })
}

render() {
    return (
    <View style={styles.container}>
    <Text style={styles.welcome}>Welcome to Light App!!</Text>
    <Text> Bulb is {this.state.isOn ? "ON": "OFF"}</Text>
    {!this.state.isOn ? <Button
    onPress={this.turnOn}
    title="Turn ON "
    color="#FF6347"
    /> :
    <Button
    onPress={this.turnOff}
    title="Turn OFF "
    color="#FF6347"
    /> }
    </View>
    );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
});
複製程式碼

重新變異程式碼並執行應用程式,您可以看到 Bulb Status 的值,當您單擊 Turn ON 時,它將顯示 Bulb 為 ON

請記住重新編譯程式碼而不是重新整理,因為我們更改了原生程式碼。

[譯] React Native 與 iOS 和 Android 通訊

Section 2 — Native Bridge in Android

在本節中,我們將使用與 iOS 相同的 Javascript 程式碼,它同樣可以應用在 Android 中。這次我們將在 Java 中建立 Bulb 類並將相同的函式 turnOn, TurnOffgetStatus 暴露給 Javascript。

開啟 Android Studio 並單擊 開啟現有的 Android Studio 專案,然後在 LightApp 中選擇 android 資料夾。下載所有 gradle 依賴項後,建立一個 Java 類 Bulb.java,如下所示:

[譯] React Native 與 iOS 和 Android 通訊

並將 Bulb.java 中程式碼更新為:

package com.lightapp;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;

public class Bulb extends ReactContextBaseJavaModule  {
    private static Boolean isOn = false;
    public Bulb(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @ReactMethod
    public void getStatus(
        Callback successCallback) {
        successCallback.invoke(null, isOn);

    }

    @ReactMethod
    public void turnOn() {
        isOn = true;
        System.out.println("Bulb is turn ON");
    }
    @ReactMethod
    public void turnOff() {
        isOn = false;
        System.out.println("Bulb is turn OFF");
    }

    @Override
    public String getName() {
        return "Bulb";
    }

}
複製程式碼

我們建立了一個 Bulb Java 類,它繼承自 ReactContextBaseJavaModuleReactContextBaseJavaModule 要求一定要實現名一個為 getName 的函式。此方法的作用是返回在 JavaScript 中表示此類的 NativeModule 的字串名稱。所以在這裡我們將呼叫 Bulb ,以便我們可以通過 JavaScript 中的 React.NativeModules.Bulb 來訪問它。我們可以使用其它不同的名稱代替 Bulb。

並非所有函式都顯式地暴露給 Javascript,要向 JavaScript 公開函式,必須使用 @ReactMethod 註解 Java 方法。橋接方法的返回型別始終為 void。

我們還建立了一個 getStatu 函式,它具有引數作為回撥,它返回一個 callback 並傳遞靜態變數 isOn 的值。

下一步是註冊模組,如果模組未註冊,則無法從 JavaScript 獲得。通過單擊選單"檔案” ->“新建” -> “Java 類”並將檔名設定為 BulbPackage 來建立檔案,然後單擊“確定”。然後將以下程式碼新增到 BulbPackage.java

package com.lightapp;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BulbPackage implements ReactPackage  {
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new Bulb(reactContext));

        return modules;
    }

}
複製程式碼

我們需要覆蓋 createNativeModules 函式並將 Bulb 物件新增到 modules 陣列中。如果這裡沒有新增,那麼它將無法在 JavaScript 中使用。

需要在 MainApplication.java 檔案的 getPackages 方法中提供 BulbPackage 包。此檔案存在於 react-native 應用程式目錄中的 android 資料夾下。在 android/app/src/main/java/com/LightApp/MainApplication.java 中更新以下程式碼

public class MainApplication extends Application implements ReactApplication {
...

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new BulbPackage()
      );
    }

....
}
複製程式碼

我們不需要更改在 iOS 中編寫的任何 JavaScript 程式碼,因為我們已經暴露了相同的類名和函式。如果您已跳過 iOS 部分,則需要從 App.js 複製 React Javascript 程式碼。

現在通過 Android Studio 或 react-native run-android 執行 App:

[譯] React Native 與 iOS 和 Android 通訊

哇唔!我們可以在螢幕上看到 Bulb 狀態,並可以從按鈕切換 ON 或 OFF。最棒的是我們建立了一個跨平臺的應用。

[譯] React Native 與 iOS 和 Android 通訊

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章