在《我们是如何将 Cordova 应用嵌入到 React Native 中》 一文中,我们简单地介绍了『React Native 重写 Cordova 插件:复杂插件的调用』步骤:
上面的 4 和 5 可以是:
4.React Native 接收到原生代码的值,并返回给原生代码 5.接收到相应的值,并发出相应的广播
本文则详细讨论一下这个过程。
这里,我们和《React Native + Cordova WebView 演进:Plugin 篇》中一样,仍然以 DatePicker 为例。
首先,我们需要一个广播:当 React Native 返回值时,我们就发出一个广播,这样可以解耦合代码。下面的代码则监听相应的广播:
$rootScope.$on('Bridge.datePicker', function(event, data) {
// 更新时间
});
然后便是相应的 datePicker 的调用:
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,并传递相应的参数。
在 WebView 的 onMessage 方法里,我们需要处理不同的 action:
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 部分代码如下所示:
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 是异步的,因此我们需要通过事件的方式进行。如下是写完插件后的调用示例:
const RNNoTagDatepicker = NativeModules.RNNoTagDatepicker;
const DatePickerEvent = new NativeEventEmitter(NativeModules.RNNoTagDatepicker);
...
const showPicker = async (options) => {
RNNoTagDatepicker.show(options);
};
如上,由于 iOS 的日期插件是异步的,并且它只能通过方法,而非组件的方式来唤醒 UI,故而需要 sendEventWithName 来返回值
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];
});
}
在这个例子里,由于在 WebView 以广播的方式解绑,因此可以直接返回值:
DatePickerEvent.addListener('DATEPICKER_NATIVE_INVOKE', (evt: Event) => {
...
webView.postMessage(JSON.stringify({
type: 'DATE_PICKER',
success: true,
date
}));
...
});
如果是要不断地发送数据,则需要在 RN 代码里执行:
let js = 'var event = new CustomEvent("' + action + '", {detail: ' + JSON.stringify(detail) + '});';
js += 'window.document.dispatchEvent(event);';
webView.injectJavaScript(js);
紧接着,就回到步骤一中的 handler:
function handler(event) {
event.target.removeEventListener('message', handler);
var data = JSON.parse(event.data);
$rootScope.$broadcast('Bridge.datePicker', data);
}
最后,我们终于到了:
$rootScope.$on('Bridge.datePicker', function(event, data) {
// 更新时间
});
如此复杂的过程,也是。。。
围观我的Github Idea墙, 也许,你会遇到心仪的项目