在做 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;
1. 初始化 RN 时 Native 向 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)
在 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
复制代码
在上述方法中, 我们通过 RCTEventEmitter 的 sendEventWithName 方法将名为 eventName 的数据 params 传递给了 JS.
提示: 在 DataToJSPresenter 中我们实现了(NSArray<NSString *> *)supportedEvents 方法, 该方法用于指定能够发送给 JS 的事件名, 所以发送给 JS 的 eventName 一定要在这个方法中进行配置否则无法发送.
实现 Native 到 JS 的通信所需要的步骤
接下来我们来总结一下, 要实现 Native 到 JS 的通信所需要的步骤:
首先要实现
- RCTEventEmitter <RCTBridgeModule>
- ;
通过 RCTEventEmitter 的 sendEventWithName 方法将数据传递给 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>
- );
- }
- }
复制代码
在上述代码中, 我们通过 NativeEventEmitter 的 addListener 添加了一个监听器, 该监听器会监听 Native 发过来的名为 testData 的数据, 这个 testData 要和上文中所讲的 eventName 要保持一致:
[self sendEventWithName:eventName body:params];
复制代码
https://coding.imooc.com/lesson/89.html#mid=2702 另外, 记得在 JS 组件卸载的时候及时移除监听器.
以上就是在 iOS 中实现 Native 到 JS 通信的原理及方式, 接下来我们来看一下实现 JS 到 Native 之间通信的原理及方式.
3. JS 到 Native 的通信(JS 发送数据给 Native)
我们所封装的 NativeModule https://coding.imooc.com/lesson/89.html#mid=2702 就是给 JS 用的, 它是一个 JS 到 Native 通信的桥梁, JS 可以通过它来实现向 Native 的通信(传递数据, 打开 Native 页面等), 接下来我就来借助 NativeModule https://coding.imooc.com/lesson/89.html#mid=2702 来实现 JS 到 Native 的通信.
关于如何实现 NativeModule 大家可以学习参考 React Native 原生模的封装 https://coding.imooc.com/lesson/89.html#mid=2702
首先实现 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
复制代码
代码解析
在 JSBridgeModule 中, 我们实现了一个
RCT_EXPORT_METHOD(sendMessage:(NSDictionary*)params)
方法, 该方法主要用于暴露给 JS 调用, 来传递数据 params 给 Native;
当收到数据后, 通过
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
通过上文所讲的 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, 在这里我们用的是 RCTPromiseResolveBlock 与 RCTPromiseRejectBlock 两种类型的回调, 分别代表成功和失败.
在 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 方法将两个整数 num1 与 num2 传递给了 Native, 然后通过 then 来监听回传结果, 整个过程采用了 Promise 的链式调用方式.
参考
实例源码下载 http://coding.imooc.com/class/89.html
React Native 开发跨平台实战 http://coding.imooc.com/class/89.html
来源: https://juejin.im/post/5b9ca5d7e51d450e9704c1d3