怎么实现从React Native向JavaScript传递原生信息

在使用 React Native 框架时,想必你一定遇到过这个问题,就是原生页面跳转至 React Native 页面的入口较多,且需要在入口页面处从原生向 JavaScript 传递信息,以便 React Native 页面的业务逻辑能够顺利进行。这个问题的解决方案有很多种,我们该如何选择最适合自己项目的一种方案呢?

目前,业界广泛使用的在 React Native 入口页向 JavaScript 传递原生信息的方案有 4 种,我们结合上节课学到的 React Native 的通信原理,来分析下这 4 种方案的优缺点,可以帮助我们更快选择出最适合自己的方案。

1. 调用原生模块获取信息

可能你第一时间想到的是编写一个原生模块,暴露一个方法由 JavaScript 端调用,将 JavaScript 端需要的原生信息返回,这也是官方文档中详细介绍的一种通信方式。这个方案就对应了 “JavaScript 端调用原生端” 通信的场景。

一般情况下,这种方案需要我们在 JavaScript 入口文件中调用原生模块的方法获取原生信息,我们把获取到的原生信息保存在数据仓库中。等到发起网络请求时,再由网络层读取数据仓库中保存的原生信息。


js

代码解读

复制代码

// 根目录 index.JavaScript 中 // 从原生模块读取原生信息 NativeModules.NativeInfoModule.getNativeInfo().then(data => { // 获取到的原生信息存储到数据仓库中 Store.setNativeInfo(data) }) // 网络层代码 const nativeInfo = Store.getNativeInfo() // 使用原生信息作为公共网络请求参数 ...

但是这个方案存在一个问题:因为 JavaScript 调用原生模块获取原生信息是一个调用链条很长的异步过程,所以如果在 JavaScript 端还没有获取到原生信息,就发生了网络请求,那么网络层从数据仓库中获取到的原生信息为空,就会导致网络请求异常。使用这个方案,我们还需要保证网络请求都在已经获取到原生信息后再继续发送。

2. 跟随页面初始 props 进行信息传递

第二个方案是在原生端跳转至 React Native 入口页面的时候,通过创建根视图时的 runApplication() 方法传递初始 props,携带原生信息。JavaScript 端可以在组件的构造方法中接收 props,并将其存入数据仓库中,等到后续的网络请求发起时,网络层读取数据仓库中保存的原生信息即可。

此方案本质上属于通信原理中的 “原生端调用 JavaScript 端” 的场景,由原生端主动发起,通过 AppRegistry 这个 JavaScript 模块的 runApplication() 方法传递信息。相比于第一个方案调用链条更短,实时性更强。


java

代码解读

复制代码

Bundle props = new Bundle(); // 将原生信息添加至 props 中 props.putString("nativeInfo", nativeInfo); // 调用根视图的 runApplication 方法,创建 React 组件并传递初始 props reactRootView.startReactApplication( getReactNativeHost().getReactInstanceManager(), componentName, props );


objectivec

代码解读

复制代码

RCTBridge *bridge = //... // 将原生信息添加至props中 NSDictionary *props = {   @"nativeInfo": nativeInfo, }; // 调用根视图的 initWithBridge: moduleName: initialProperties 方法,创建 RCTRootView 组件并传递初始 props RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:componentName initialProperties:props];


plain

代码解读

复制代码

export default class extends Component { constructor(props) { super(props); const { nativeInfo } = props; // 获取到的原生信息存储到数据仓库中 nativeInfo && Store.setNativeInfo(nativeInfo); } render() { // ... } } // 网络层代码 const nativeInfo = Store.getNativeInfo() // 使用原生信息作为公共网络请求参数 ...

这个方案有一个优点,就是在组件构造时就能够获取到原生信息,这通常早于发起网络请求的时间点,所以相比于第一个方案,我们不需要处理原生信息还未获取到就发生了网络请求的场景。

但是这个方案也有一个缺点,如果存在多个 React Native 的入口页面,原生端就需要在每一个入口页面的地方将原生信息作为初始 props 进行传递,如果入口页面变更了,比如说原生页面逐步替换为 React Native 页面的场景,可能这次需求中这个页面是入口页,下一次需求迭代后入口页就变成了前一个页面,这个时候,我们还需要频繁修改入口页接收原生信息的逻辑,带来了维护成本。

3. 原生端发送事件进行信息传递

第三个方案是通过 React Native 提供的事件通信机制进行信息传递。React Native 提供了 NativeEventEmitter 进行原生端与 JavaScript 端的事件通信机制。JavaScript 端可以在入口处注册一个事件的监听器,原生端在事件监听器注册完毕后向 JavaScript 端发送事件,通过事件携带的参数来传递原生信息。JavaScript 端可以在注册的事件监听器中定义接收事件时的回调,此时可以将原生信息存入数据仓库,以便网络层调用。


plain

代码解读

复制代码

ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager(); reactInstanceManager.getCurrentReactContext() .getJavaScriptModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("NativeInfoEvent", nativeInfo);


js

代码解读

复制代码

// 创建一个 OC 的 Class 继承于 RCTEventEmitter @interface EventEmitter : RCTEventEmitter <RCTBridgeModule> @end @implementation EventEmitter RCT_EXPORT_MODULE(); // 声明原生事件的名称 - (NSArray<NSString *> *)supportedEvents {   return @[@"NativeInfoEvent"]; } - (void)emit { // 发送事件     [self sendEventWithName:@"NativeInfoEvent" body: nativeInfo]; } @end


js

代码解读

复制代码

// 根目录 index.JavaScript 中 // 注册事件监听器接收原生信息 const emitter = new NativeEventEmitter(NativeInfoModule); emitter.addListener('NativeInfoEvent', (data) => { // 获取到的原生信息存储到数据仓库中 Store.setNativeInfo(data); emitter.removeListener('NativeInfoEvent'); }); // 网络层代码 const nativeInfo = Store.getNativeInfo(); // 使用原生信息作为公共网络请求参数 ...

这个方案使用的通信原理也是 “原生端调用 JavaScript 端” 的场景,由原生端主动发起信息的传递。但是如果使用这个方案,我们需要注意 JavaScript 端注册完事件监听器后,原生端再发送的原生信息才会被接收。这个问题是由 NativeEventEmitter 的事件分发机制造成的。

当然你也可以自己编写一个专门用于接收原生信息的 JavaScript 模块来解决这个问题。原生端通过这个 JavaScript 模块的方法进行原生信息的写入,JavaScript 端直接从 JavaScript 模块中进行读取使用,这样还省去了存储到数据仓库的步骤。

4. 原生端设置 JavaScript 端全局变量

经过调研我们发现 React Native 框架是通过 setGlobalVariable() 方法将信息直接挂载到了 JavaScript 环境的全局 global 对象下。所以,第四种方案就是利用 setGlobalVariable() 这个方法将想要传递的原生信息直接挂载到 JavaScript 环境的全局 global 对象下,以供 JavaScript 端直接读取利用。


js

代码解读

复制代码

ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager(); // 添加 RN 实例初始化完成的回调 reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext context) { // 挂载原生信息至 JavaScript 端的全局 global 对象下 context.getCatalystInstance().setGlobalVariable("nativeInfo", nativeInfo); } });


js

代码解读

复制代码

@interface RCTCxxBridge (GlobalVariable) // 暴露 reactInstance 属性 - (std::weak_ptr<Instance>)reactInstance; // 新增设置全局变量方法供业务方调用 - (void)setGlobalVariable:(NSString *)key value:(NSString *)value; @end @implementation RCTCxxBridge (GlobalVariable) - (void)setGlobalVariable:(NSString *)key value:(NSString *)value {   [self reactInstance]->setGlobalVariable(       [key UTF8String],       std::make_unique<JSBigStdString>([value UTF8String]))   ); } @end // 挂载原生信息至 JavaScript 端的全局 global 对象下 RCTCxxBridge *bridge = //... [bridge setGlobalVariable:@"nativeInfo" value: nativeInfo];


javascript

代码解读

复制代码

// 网络层代码 const nativeInfo = global.nativeInfo; // 使用原生信息作为公共网络请求参数 ...

这个方案直接利用了桥接层 JavaScriptC 提供的 setGlobalVariable() 方法,将原生信息直接挂载到了 JavaScript 端的全局 global 对象下,通信效率最高,而且时效性最高。但是这个方案在使用 Chrome 进行 Remote JavaScript Debugging 的时候会出现一个情况,global 下挂载的信息会变为 undefined,这会给调试带来一些麻烦。

总结

以上就是在 React Native 入口页向 JavaScript 传递原生信息的 4 种方案,最后我再结合 React Native 的通信原理,简单给你总结一下这 4 种方案的优缺点。你可以从这张表格中,直观地看到这 4 种方案的通信效率对比、是否需要考虑时机问题、是否需要处理多入口场景以及是否影响调试。

其中,被广泛应用的原生模块获取信息方案,由于调用链条过长所以通信效率最低。使用原生模块获取信息方案和原生端发送事件传递信息的方案,都需要我们考虑信息传递的时机问题。跟随页面初始 props 进行信息传递方案,在通信效率和是否考虑时机问题方面均表现较好,但如果存在多个 React Native 页面入口,就需要更多的维护成本。如果采用原生端直接设置 JavaScript 端全局变量的方案,则会给调试工作造成更多的麻烦。

你可以根据自己项目的实际情况选取最适合的方案。同时,也希望你在解决这个问题的过程中,对 React Native 框架中 JavaScript 与 Native 的通信原理能有更深地理解。

原文:https://juejin.cn/post/7490510447142813732

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值