那么书接上回,今天主要是继续探究React Native
与原生模块的架构方式。
原生模块
原生模块可以访问activity
、运行环境、GPS、存储空间等。原生模块就是能够在JavaScript
层调用的API。
因为对原生模块的全部请求都要异步执行。如果原生方法需要为JavaScript
层的调用返回数据,该操作将通过promise
或者回调函数来完成。React Native
为这两种方式都提供了接口。
剖析原生模块
IOS
和Android
对原生模块的实现思路是这样的:
- 每个模块继承自原生模块父类。
- 每个模块都定义了名称,以便
JavaScript
层访问。 - 每个模块都导出可调用的方法,并包含
Java
的注释或Objective-C
的宏。
01. Android
Android
平台的原生模块继承ReactContextBaseJavaModule
,并且必须实现getName
方法,它的返回值作为JavaScript
模块的名称。
任何允许JavaScript
层调用的方法,都带有@ReactMethod
注释。React
的内部机制会把JavaScript
层的请求映射到这些方法上。每个方法的身份由它的签名、名称以及参数进行鉴别。方法所需的任何参数都会被转换成对应的属性类型。
简单的示例在上一篇已经提及,请参阅React Native与原生模块、组件之间的关系浅析
02. JavaScript
JavaScript
层把原生模块作为NativeModules
对象的一个属性,并且任何带有@ReactMethod
注释或者属于RCT_EXPORT_METHOD
宏的方法都能够被调用。
参数
原生模块的方法和普通方法一样可以接受参数。
针对于Android
,带有@ReactMethod
注释的方法支持以下参数类型。
Boolean
->Bool
Integar
->Number
Double
->Number
Float
->Number
String
->String
Callback
->function
ReadableMap
->Object
ReadableArray
->Array
回调函数和promise
由于所有的通信过程都是异步的,原生模块不能有返回值。React Native
使用回调函数和promise
类作为解决方案。使用方式与在JavaScript代码中执行回调函数以及promise
的resolve
/reject
操作完全一样。
01 回调函数
原生回调函数接口遵循两个参数的约定:
- 第一参数表示错误对象(没有错误的情况则为null)。
- 第二参数用来提供要响应的数据。
示例:
@ReactMethod
public void add (int a, int b, Callback callback) {
int sum = a + b;
callback.invoke(null, sum);
}
02 promise
promise
可以在操作成功后把值返回给JavaScript
层。响应要么成功要么失败。JavaScript
层的接口保留了我们所熟悉的.then
和.catch
方法。
示例:
@ReactMethod
public void failIfFalse (boolean value, Promise promise) {
if (value) {
promise.resolve("Your value was true, so it resolved.");
} else {
promise.reject(new Error("Your value was false, so it rejected"));
}
}
03 返回请求成功的对象
我们已经了解到,可以通过回调函数和promise
在请求成功后把标量值返回给JavaScript
层。大多数情况下响应内容应该是JSON对象的形式。
Android
提供了WritableMap
来返回JSON响应。该接口和包含特定类型put方法的Map很相似。
pushNull
putBoolean
putDouble
putInt
putString
putArray
putMap
String
类型的键名作为每个方法的第一参数。
第二参数是方法名相关类型的值。
示例:
@ReactMethod
public void jsonResponse(Promise promise) {
WritableMap jsonMap = new WritableNativeMap();
jsonMap.putString("stringValue", "A string");
jsonMap.putBoolean("booleanValue", true);
jsonMap.putInt("intValue", 123);
promise.resolve(jsonMap);
}
常量
原生模块也可以导出常量,供JavaScript
访问。
要使原生模块的常量可用,需要实现父类的getConstants
(Android
)或constantsToExport
(iOS
)方法,这两个方法将返回键值对的映射,键名会作为JavaScript
模块的属性而存在。
示例:
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("HELLO", "world");
constants.put("FOO", "bar");
}
事件
DeviceEventEmitter
模块用于把事件从原生层传到JavaScript
层。
针对Android
而言,ReactContext
暴露出getJSModule
方法用于获取其他模块,为它传入类参数后返回相应的实例。它将用来获取RCTDeviceEventEmitter
模块并调用emit
方法,接着事件就会通过桥接层发送到JavaScript
层,DeviceEventEmitter
模块的监听器就会捕获这个事件,并通知其他任意的事件监听器。
示例:
@ReactMethod
public void sendTestEvent() {
String eventName = "customEvent";
WritableMap params = new WritableNativeMap();
params.putString("param", "Hello world");
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
在JavaScript
上面的调用方式如下:
import {DeviceEventEmitter, NativeModules} from 'react-native';
const {ExampleModule} = NativeModules;
DeviceEventEmitter.addListener('customEvent', e => {
console.log('received event', e.nativeEvent.param); // 2
});
ExampleModules.sendTestEvent(); // 1
总结
通过本篇的学习,了解了原生模块和组件的工作原理。知道了react native
与原生层的调用关系。原生模块就是JavaScript
层可以调用的一些方法的集合。