一、前言
有时候我们的应用需要进行访问原生平台系统的API接口,但是React Native可能还没有封装相应功能组件。还有可能我们需要去复用一些原生java代码而不是让JavaScript重新去实现一遍。或者我们可能需要些一些更加高级的功能代码,所线程相关的。例如:图片处理,数据库以及一些高级功能扩展之类的。
React Native平台的开发其实本身也是可以让你写纯原生代码并且还可以让你访问原生平台的功能。这是一个比较高级的功能不过官方还是不推荐你在平时开发中使用这样的开发形式。但是如果你具备这样的开发能力,也是还是不错的。特别当在React Native暂时未提供部分原生功能或者模块,那么你可以使用这样的方法进行封装扩展。今天我们就来看一下原生组件的封装扩展方法。
二、实例Toast模块
现在我们来拿Android原生Toast模块进行举例子。进行封装之后,通过JavaScript来进行弹出toast消息。
我们首先来创建一个原生模块。一个原生模块是一个继承了ReactContextBaseJavaModule
的 Java 类,它可以实现一些 JavaScript 所需的功能。我们这里的目标是可以在 JavaScript 里写ToastExample.show('Awesome', ToastExample.SHORT);
,来调起一个短暂的 Toast 通知。
创建一个新的 Java 类并命名为ToastModule.java
import android.widget.Toast;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.HashMap;
import java.util.Map;
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 端标记这个模块。这里我们把这个模块叫做ToastExample
,这样就可以在 JavaScript 中通过NativeModules.ToastExample
访问到这个模块。
注意:RN 已经内置了一个名为 ToastAndroid 的模块,所以在练习时请勿使用 ToastAndroid 的名字,否则运行时会报错名字冲突!
@Override
public String getName() {
return "ToastExample";
}
一个可选的方法getContants
返回了需要导出给 JavaScript 使用的常量。它并不一定需要实现,但在定义一些可以被 JavaScript 同步访问到的预定义的值时非常有用。
@Override
public Map getConstants() {
final Map constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
return constants;
}
最后在写一个方法,需要添加@ReactMethod
注解,用于给JavaScript进行调用。该桥接方法的返回类型必须要为void。React Native中的桥接通信是异步形式的,所以如果需要返回值给JavaScript必须需要通过回调方法或者事件发送(此类方法会在后面的文章中讲解)
/**
* 该方法用于给JavaScript进行调用
* @param message
* @param duration
*/
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
三、参数类型说明
上面我们讲解到@ReactMethod
注解的方法,Java和JavaScript中的数据类型还是有一些区别,下面我们看一下Java和JavaScript的参数类型的对应问题,总体对应方式如下:
- Boolean -> Bool
- Integer -> Number
- Double -> Number
- Float -> Number
- String -> String
- Callback -> function
- ReadableMap -> Object
- ReadableArray -> Array
更多信息大家可以点击查看ReadableMap和ReadableArray。
四、模块注册
前面第二步骤我们已经完成组件模块的定义以及对外方法的封装提供,下面在原生代码这边就需要进行注册该模块。我们需要自定义一个实现ReactPackage
的类,同时需要实现这里边的三个方法。最重要的是在createNativeModule
方法中添加刚刚那个模块。如果没有注册,那么当前模块在JavaScript中是无法被访问到的。具体参考代码如下:
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 CustomToastPackage implements ReactPackage {
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List createNativeModules(
ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new ToastModule(reactContext));
modules.add(new IntentModule(reactContext));
return modules;
}
}
接下来,上面自定义的packager需要在MainActivity.java
中的getPackagers
方法中进行添加
// MainApplication.java
...
import com.your-app-name.CustomToastPackage; // <-- 引入你自己的包
...
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new CustomToastPackage()); // <-- 添加这一行,类名替换成你的Package类的名字.
}
不过为了在JavaScript实现当前封装的模块可以使用方便,通常会把该封装成JavaScript模块。这样可以省下每次访问组件都要加入NativeModules
的步骤。具体方式如下:
// ToastExample.js
/**
* 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";
// 下一句中的ToastExample即对应上文
// public String getName()中返回的字符串
module.exports = NativeModules.ToastExample;
然后在别的JavaScript文件代码中就可以如下进行访问了:
import ToastExample from "./ToastExample";
ToastExample.show("Awesome", ToastExample.SHORT);
那么具体在JavaScript文件中的调用方法实例代码如下:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button
} from 'react-native';
import ToastExample from './ToastExample';
export default class App extends Component {
render() {
return (
RN的界面
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});
运行效果如下:
源代码地址