有时候一个应用程序需要访问一个平台API,React Native还没有相应的模块。 也许你想重用一些现有的Java代码,而不必在JavaScript中重新实现它,或者编写一些高性能,多线程的代码,例如图像处理,数据库或任何数量的高级扩展。
我们设计了React Native,因此您可以编写真实的本地代码,并可以访问平台的全部功能。 这是一个更高级的功能,我们并不期望它成为通常开发过程的一部分,但它是必不可少的。 如果React Native不支持您需要的本地功能,您应该可以自己构建它。
如果您打算对Java代码进行更改,我们建议启用Gradle Daemon来加速构建。
Toast模块
下面构建Toast示例。 假设我们希望能够从JavaScript创建Toast消息。
我们首先创建一个本地模块。 本地模块是一个Java类,通常扩展ReactContextBaseJavaModule类并实现JavaScript所需的功能。 我们的目标是能写ToastExample.show('Awesome',ToastExample.SHORT); 从JavaScript在屏幕上显示一个简短的Toast。
package com.facebook.react.modules.toast;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class ToastModule extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
}
ReactContextBaseJavaModule要求实现一个名为getName的方法。 此方法的目的是返回在JavaScript中代表此类的NativeModule的字符串名称。 所以在这里我们将调用这个ToastExample,以便我们可以通过JavaScript中的React.NativeModules.ToastExample来访问它。
public String getName() {
return "ToastExample";
}
一个名为getConstants的可选方法返回暴露给JavaScript的常量值。 它的实现不是必需的,但是对于需要从JavaScript同步到Java的关键预定义值非常有用。
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
return constants;
}
要将方法公开到JavaScript,必须使用@ReactMethod对Java方法进行注释。 桥接方法的返回类型总是无效的。 React Native桥是异步的,因此将结果传递给JavaScript的唯一方法是使用回调或发送事件(请参见下文)。
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
参数类型
以@ReactMethod注解的方法支持以下参数类型,并直接映射到它们的JavaScript等效项
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
阅读更多关于 ReadableMap 和ReadableArray
注册模块
Java中的最后一步是注册模块; 这发生在您的应用程序包的createNativeModules中。 如果一个模块没有被注册,它将不能从JavaScript获得。
package com.facebook.react.modules.toast;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AnExampleReactPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastModule(reactContext));
return modules;
}
}
该包需要在MainApplication.java文件的getPackages方法中提供。 该文件存在于react-native应用程序目录中的android文件夹下。 这个文件的路径是:android / app / src / main / java / com / your-app-name / MainApplication.java。
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new AnExampleReactPackage()); // <-- Add this line with your package name.
}
为了更简单地从JavaScript访问您的新功能,通常会将本地模块包装到JavaScript模块中。 这不是必须的,但是可以节省您的库的用户每次都需要将其从NativeModules中提取出来。 这个JavaScript文件也成为您添加JavaScript端功能的好地方。
/**
* This exposes the native ToastExample module as a JS module. This has a
* function 'show' which takes the following parameters:
*
* 1. String message: A string with the text to toast
* 2. int duration: The duration of the toast. May be ToastExample.SHORT or
* ToastExample.LONG
*/
import {NativeModules} from 'react-native';
module.exports = NativeModules.ToastExample;
现在,从您的其他JavaScript文件中,您可以调用像这样的方法:
import ToastExample from './ToastExample';
ToastExample.show('Awesome', ToastExample.SHORT);
Toasts之外
回调
本地模块也支持一种特殊的参数 - 回调。 在大多数情况下,它被用来将函数调用结果提供给JavaScript。
import com.facebook.react.bridge.Callback;
public class UIManagerModule extends ReactContextBaseJavaModule {
...
@ReactMethod
public void measureLayout(
int tag,
int ancestorTag,
Callback errorCallback,
Callback successCallback) {
try {
measureLayout(tag, ancestorTag, mMeasureBuffer);
float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
successCallback.invoke(relativeX, relativeY, width, height);
} catch (IllegalViewOperationException e) {
errorCallback.invoke(e.getMessage());
}
}
...
它的方法将被访问在JavaScript中使用:
UIManager.measureLayout(
100,
100,
(msg) => {
console.log(msg);
},
(x, y, width, height) => {
console.log(x + ':' + y + ':' + width + ':' + height);
}
);
本地模块应该只调用一次回调。 但是,它可以存储回调并稍后调用它。
突出显示在本地函数完成后不立即调用回调是非常重要的 - 请记住,桥接通信是异步的,这也与运行循环有关。
承诺(Promises)
原生模块也可以履行承诺,这可以简化您的代码,特别是在使用ES2016的异步/等待语法时。 当桥接本地方法的最后一个参数是Promise时,其相应的JS方法将返回一个JS Promise对象。
重构上面的代码来使用promise而不是回调看起来像这样:
import com.facebook.react.bridge.Promise;
public class UIManagerModule extends ReactContextBaseJavaModule {
...
private static final String E_LAYOUT_ERROR = "E_LAYOUT_ERROR";
@ReactMethod
public void measureLayout(
int tag,
int ancestorTag,
Promise promise) {
try {
measureLayout(tag, ancestorTag, mMeasureBuffer);
WritableMap map = Arguments.createMap();
map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0]));
map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1]));
map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2]));
map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3]));
promise.resolve(map);
} catch (IllegalViewOperationException e) {
promise.reject(E_LAYOUT_ERROR, e);
}
}
...
该方法的JavaScript对应方返回一个Promise。 这意味着您可以在异步函数中使用await关键字来调用它并等待其结果:
async function measureLayout() {
try {
var {relativeX, relativeY, width, height} = await UIManager.measureLayout(
100,
100
);
console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);
} catch (e) {
console.error(e);
}
}
measureLayout();
线程(Threading)
原生模块不应该对它们被调用的线程有任何假设,因为当前的分配将来可能会发生变化。 如果需要阻塞调用,则应该将繁重的工作分派给内部管理的工作者线程,并从那里分配任何回调。