React Native iOS混合開發實戰教程

CrazyCodeBoy發表於2018-09-15

在做RN開發的時候通常離不了JS 和Native之間的通訊,比如:初始化RN時Native向JS傳遞資料,JS呼叫Native的相簿選擇圖片,JS呼叫Native的模組進行一些複雜的計算,Native將一些資料(GPS資訊,陀螺儀,感測器等)主動傳遞給JS等。

在這篇文章中我將向大家介紹在RN中JS和Native之間通訊的幾種方式以及其原理和使用技巧;

接下來我將分場景來介紹JS 和Native之間的通訊。

幾種通訊場景:

  • 初始化RN時Native向JS傳遞資料;
  • Native傳送資料給JS;
  • JS傳送資料給Native;
  • JS傳送資料給Native,然後Native回傳資料給JS;

React-Native-JS-Native-Communication

1. 初始化RN時Native向JS傳遞資料

init-data-to-js

在RN的API中提供了Native在初始化JS頁面時傳遞資料給JS的方式,這種傳遞資料的方式比下文中所講的其他幾種傳遞資料的方式發生的時機都早。

因為很少有資料介紹這種方式,所以可能有很多朋友還不知道這種方式,不過不要緊,接下來我就向大家介紹如何使用這種方式來傳遞資料給JS。

概念

RN允許我們在初始化JS頁面時向頂級的JS 元件傳遞props資料,頂級元件可以通過this.props來獲取這些資料。

iOS

[[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                moduleName: self.moduleName //這個"App1"名字一定要和我們在index.js中註冊的名字保持一致
                         initialProperties:@{@"params":self.paramsInit}//RN初始化時傳遞給JS的初始化資料
                             launchOptions: nil];
複製程式碼

接下來,我們先來看一下如何在iOS上來傳遞這些初始化資料。

iOS向RN傳遞初始化資料initialProperties

RN的RCTRootView提供了initWithBundleURL方法來渲染一個JS 元件,在這個方法中提供了一個用於傳遞給這個JS 元件的初始化資料的引數。

方法原型:

- (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName
		initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions
複製程式碼
  • jsCodeLocation:要渲染的RN的JS頁面的路徑;
  • moduleName:要載入的JS模組名;
  • initialProperties:要傳遞給頂級JS元件的初始化資料;
  • launchOptions:主要在AppDelegate載入JS Bundle時使用,這裡傳nil就行;

通過上述方法的第三個引數就可以將一個NSDictionary型別的資料傳遞給頂級JS元件

示例程式碼:

[[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                moduleName: self.moduleName
                         initialProperties:@{@"params":@"這是傳遞給頂級JS元件的資料"}//RN初始化時傳遞給JS的初始化資料
                             launchOptions: nil];
複製程式碼

在上述程式碼中,我們將一個名為params的資料這是傳遞給頂級JS元件的資料傳遞給了頂級的JS 元件,然後在頂級的JS 元件中就可以通過如下方法來獲取這個資料了:

 render() {
        const {params}=this.props;
        return (
            <View style={styles.container}>
                <Text style={styles.data}>來自Native初始化資料:{params}</Text>
            </View>
            );
 }
複製程式碼

另外,如果要在非頂級頁面如CommonPage中使用這個初始化資料,則可以通過如下方式將資料傳遞到CommonPage頁面:

export default class App extends Component<Props> {
	...
    render() {
        return <CommonPage  {...this.props}/>;
    }
    ...
}
複製程式碼

2. Native到JS的通訊(Native傳送資料給JS)

init-data-to-js

在RN的iOS SDK中提供了一個RCTEventEmitter介面,我們可以通過該介面實現Native到JS的通訊,也就是Native將資料傳遞給JS。

方法原型:

- (void)sendEventWithName:(NSString *)name body:(id)body;
複製程式碼

所以只要我們獲得RCTEventEmitter的例項就可以藉助它將資料傳遞給JS。為了獲得RCTEventEmitter的例項我們可以通過繼承RCTEventEmitter <RCTBridgeModule>的方式來實現:

DataToJSPresenter.h

/**
 * React Native JS Native通訊
 * Author: CrazyCodeBoy
 * 視訊教程:https://coding.imooc.com/lesson/89.html#mid=2702
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface DataToJSPresenter : RCTEventEmitter <RCTBridgeModule>

@end
複製程式碼

DataToJSPresenter.m

/**
 * React Native JS Native通訊
 * Author: CrazyCodeBoy
 * 視訊教程:https://coding.imooc.com/lesson/89.html#mid=2702
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */
#import "DataToJSPresenter.h"

@implementation DataToJSPresenter

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
    return @[@"testData"];
}
- (instancetype)init {
    if (self = [super init]) {//在module初始化的時候註冊fireData廣播
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fireData:) name:@"fireData" object:nil];
    }
    return self;
}
- (void)fireData:(NSNotification *)notification{//傳送資料給RN
    NSString *eventName = notification.object[@"name"];
    NSDictionary *params = notification.object[@"params"];
    [self sendEventWithName:eventName body:params];
}

@end
複製程式碼

在上述方法中,我們通過RCTEventEmittersendEventWithName方法將名為eventName的資料params傳遞給了JS。

提示:在DataToJSPresenter中我們實現了(NSArray<NSString *> *)supportedEvents方法,該方法用於指定能夠傳送給JS的事件名,所以傳送給JS的eventName一定要在這個方法中進行配置否則無法傳送。

實現Native到JS的通訊所需要的步驟

接下來我們來總結一下,要實現Native到JS的通訊所需要的步驟:

  • 首先要實現RCTEventEmitter <RCTBridgeModule>
  • 通過RCTEventEmittersendEventWithName方法將資料傳遞給JS;

通過上述步驟,我們就可以將資料從Native發動到JS,那麼如何在JS中來獲取這些資料呢?

在JS中獲取Native通過RCTEventEmitter傳過來的資料

在JS中可以通過NativeEventEmitter來獲取Native通過RCTEventEmitter傳過來的資料,具體方法如下:

import {NativeEventEmitter} from 'react-native';
export default class CommonPage extends Component<Props> {
    constructor(props) {
        super(props);
        this.state = {
            data: "",
            result: null
        }
    }

    componentWillMount() {
        this.dataToJSPresenter = new NativeEventEmitter(NativeModules.DataToJSPresenter);
        this.dataToJSPresenter.addListener('testData', (e) => {// for iOS
            this.setState({
                data: e.data
            })
        })
	}

    componentWillUnmount() {
        if (this.dataToJSPresenter){
            this.dataToJSPresenter.removeListener('testData');
        }
    }

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.data}>收到Native的資料:{this.state.data}</Text>
            </View>
        );
    }
}
複製程式碼

在上述程式碼中,我們通過NativeEventEmitteraddListener新增了一個監聽器,該監聽器會監聽Native發過來的名為testData的資料,這個testData要和上文中所講的eventName要保持一致:

[self sendEventWithName:eventName body:params];
複製程式碼

coding.imooc.com/lesson/89.h… 另外,記得在JS元件解除安裝的時候及時移除監聽器。

以上就是在iOS中實現Native到JS通訊的原理及方式,接下來我們來看一下實現JS到Native之間通訊的原理及方式。

3. JS到Native的通訊(JS傳送資料給Native)

init-data-to-js

我們所封裝的NativeModule就是給JS用的,它是一個JS到Native通訊的橋樑,JS可以通過它來實現向Native的通訊(傳遞資料,開啟Native頁面等),接下來我就來藉助NativeModule來實現JS到Native的通訊。

關於如何實現NativeModule大家可以學習參考React Native原生模的封裝

首先實現JSBridgeModule

首先我們需要實現RCTBridgeModule

JSBridgeModule.h

/**
 * React Native JS Native通訊
 * Author: CrazyCodeBoy
 * 視訊教程:https://coding.imooc.com/lesson/89.html#mid=2702
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */
#import <React/RCTBridgeModule.h>
@interface JSBridgeModule : NSObject <RCTBridgeModule>

@end
複製程式碼

JSBridgeModule.m

/**
 * React Native JS Native通訊
 * Author: CrazyCodeBoy
 * 視訊教程:https://coding.imooc.com/lesson/89.html#mid=2702
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */
#import "JSBridgeModule.h"

@implementation JSBridgeModule

RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();//讓RN在主執行緒回撥這些方法
}
RCT_EXPORT_METHOD(sendMessage:(NSDictionary*)params){//接受RN發過來的訊息
    [[NSNotificationCenter defaultCenter] postNotificationName:@"sendMessage" object:params];
}
@end
複製程式碼

程式碼解析

  1. JSBridgeModule中,我們實現了一個RCT_EXPORT_METHOD(sendMessage:(NSDictionary*)params)方法,該方法主要用於暴露給JS呼叫,來傳遞資料params給Native;
  2. 當收到資料後,通過NSNotificationCenter以通知的形式將資料傳送出去;

JS呼叫JSBridgeModule傳送資料給Native

import {NativeModules} from 'react-native';

const JSBridge = NativeModules.JSBridgeModule;

JSBridge.sendMessage({text: this.text})
複製程式碼

通過上述程式碼我就可以將一個Map型別的資料{text: this.text}傳遞給Native。

4. JS傳送資料給Native,然後Native回傳資料給JS

init-data-to-js

通過上文所講的JS到Native的通訊(JS傳送資料給Native),我們已經實現了JS到Native的通訊,當時我們藉助的是JSBridgeModule,其實它的功能還不侷限於此,藉助它我們還可以實現Native到JS的資料回傳。

在Native的實現

JSBridgeModule中新增如下方法:

RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSInteger result=num1+num2;
    resolve([NSString stringWithFormat:@"%ld",(long)result]);//回撥JS
}
複製程式碼

上述程式碼暴露給了JS一個簡單的兩個整數之間的加法運算,並將運算結果回傳給JS,在這裡我們用的是RCTPromiseResolveBlockRCTPromiseRejectBlock兩種型別的回撥,分別代表成功和失敗。

在JS中的實現

import {NativeModules} from 'react-native';

const JSBridge = NativeModules.JSBridgeModule;
JSBridge.doAdd(parseInt(this.num1), parseInt(this.num2)).then(e => {
    this.setState({
        result: e
    })
})
複製程式碼

在JS中我們通過JSBridge.doAdd方法將兩個整數num1num2傳遞給了Native,然後通過then來監聽回傳結果,整個過程採用了Promise的鏈式呼叫方式。

參考

相關文章