在《我们是如何将 Cordova 应用嵌入到 React Native 中》 一文中,我们简单地介绍了『React Native 重写 Cordova 插件:复杂插件的调用』步骤:
- WebView 调用 RN 方法,并监听 React Native 返回的相应事件
- React Native 接收到 WebView 的调用,调用原生代码,并监听原生代码返回的相应事件
- 原生代码执行 React Native 调用的方法,并响应事件给 React Native
- React Native 接收到原生代码的值,执行 injectJavaScript 注入代码到 WebView 里并执行
- 注入的 JavaScript 执行代码,并发出相应的广播
- WebView 调用的地方,接收到广播,执行相应的方法
上面的 4 和 5 可以是:
4.React Native 接收到原生代码的值,并返回给原生代码 5.接收到相应的值,并发出相应的广播。
即:
本文则详细讨论一下这个过程。
步骤1:WebView 调用 RN 方法,并监听 React Native 返回的相应事件
这里,我们和《React Native Cordova WebView 演进:Plugin 篇》中一样,仍然以 DatePicker 为例。
首先,我们需要一个广播:当 React Native 返回值时,我们就发出一个广播,这样可以解耦合代码。下面的代码则监听相应的广播:
代码语言:javascript复制$rootScope.$on('Bridge.datePicker', function(event, data) {// 更新时间});
然后便是相应的 datePicker 的调用:
代码语言:javascript复制function datePicker(options) { function handler(event) { event.target.removeEventListener('message', handler); var data = JSON.parse(event.data); $rootScope.$broadcast('Bridge.datePicker', data); } window.document.addEventListener('message', handler); window.postMessage(JSON.stringify({ action: 'DATE_PICKER', payload: payload }));}
先监听从 React Native 发过来的内容,当接收到内容将数据以广播的形式发出。然后,再通过 PostMessage 告诉 React Naitve,我们需要在调用哪个 action,并传递相应的参数。
步骤2:React Native 接收到 WebView 的调用,调用原生代码,并监听原生代码返回的相应事件
在 WebView 的 onMessage 方法里,我们需要处理不同的 action:
代码语言:javascript复制onMessage = (evt, webView) => { const event = JSON.parse(evt.nativeEvent.data); const action = event.action; const payload = event.payload ... switch (action) { case 'DATE_PICKER': { return DatePickerHandler.showDatePicker(payload, webView); } } ...}
然后根据传过来的 action 类型,调用相应的方法,如这里是 DatePickerHandler.showDatePicker,其 Android 部分代码如下所示:
代码语言:javascript复制 const { action, year, month, day } = await DatePickerAndroid.open(options); if (action !== DatePickerAndroid.dismissedAction) { webView.postMessage(JSON.stringify({ type: 'DATE_PICKER', success: true, date, })); }
iOS 则有一些不同,iOS 没有非标签的组件,需要自己写。而且,由于 iOS 的 DatePicker 是异步的,因此我们需要通过事件的方式进行。如下是写完插件后的调用示例:
代码语言:javascript复制const RNNoTagDatepicker = NativeModules.RNNoTagDatepicker;const DatePickerEvent = new NativeEventEmitter(NativeModules.RNNoTagDatepicker);...const showPicker = async (options) => { RNNoTagDatepicker.show(options);};
步骤3:原生代码执行 React Native 调用的方法,并响应事件给 React Native
如上,由于 iOS 的日期插件是异步的,并且它只能通过方法,而非组件的方式来唤醒 UI,故而需要 sendEventWithName 来返回值
代码语言:javascript复制RCT_EXPORT_METHOD(show:(NSDictionary *) options) { dispatch_async(dispatch_get_main_queue(), ^{ }}#pragma mark - Actions- (IBAction)doneAction:(id)sender { dispatch_async(dispatch_get_main_queue(), ^{ NSTimeInterval seconds = [self.datePicker.date timeIntervalSince1970]; [self sendEventWithName:@"DATEPICKER_NATIVE_INVOKE" body: @{@"status": @"success", @"value": [NSString stringWithFormat:@"%f", seconds]}]; [self hide]; });}
步骤4:React Native 接收到原生代码的值,并返回给原生代码
在这个例子里,由于在 WebView 以广播的方式解绑,因此可以直接返回值:
代码语言:javascript复制DatePickerEvent.addListener('DATEPICKER_NATIVE_INVOKE', (evt: Event) => { ... webView.postMessage(JSON.stringify({ type: 'DATE_PICKER', success: true, date })); ...});
如果是要不断地发送数据,则需要在 RN 代码里执行:
代码语言:javascript复制let js = 'var event = new CustomEvent("' action '", {detail: ' JSON.stringify(detail) '});';js = 'window.document.dispatchEvent(event);';webView.injectJavaScript(js);
步骤5:接收到相应的值,并发出相应的广播
紧接着,就回到步骤一中的 handler:
代码语言:javascript复制 function handler(event) { event.target.removeEventListener('message', handler); var data = JSON.parse(event.data); $rootScope.$broadcast('Bridge.datePicker', data); }
步骤6:WebView 调用的地方,接收到广播,执行相应的方法
最后,我们终于到了:
代码语言:javascript复制$rootScope.$on('Bridge.datePicker', function(event, data) {// 更新时间});
如此复杂的过程,也是。。。
好了,就差一个小程序框架了:
《如何创建一个兼容「微信小程序」的Web框架:WIN》