React Native iOS混合开发实战教程

5,956 阅读7分钟

在做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的链式调用方式。

参考