在做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
在上述方法中,我们通过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)
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
代码解析
- 在
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
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,在这里我们用的是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
的链式调用方式。