如何在 react native 模块中写一个自定义模块

Android React Native使用原生模块
有时候我们的App需要访问平台API,并且React Native可能还没有相应的模块包装;或者你需要复用一些代码,而不是用Javascript重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、、或者各种高级扩展等等。
而用React Native可以在它的基础上编写真正原生的代码,并且可以访问平台所有的能力。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。
不过在开始编写代码使用原生模块前,有一个知识点需要掌握,免得又坑进去了。
在使用React Native的时候,经常会看到这么一段代码
var React = require('react-native');
那么require这个语句的作用到底是什么呢,下面的流程提取自require() 解读
当遇到 require(X) 时,按下面的顺序处理。
(1)如果 X 是内置模块(比如 require(‘http’))
  a. 返回该模块。
  b. 不再继续执行。
(2)如果 X 以 “./” 或者 “/” 或者 “../” 开头
a. 根据 X 所在的父模块,确定 X 的绝对路径。
b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
X/package.json(main字段)
X/index.js
X/index.json
X/index.node
(3)如果 X 不带路径
  a. 根据 X 所在的父模块,确定 X 可能的安装目录。
  b. 依次在每个目录中,将 X 当成文件名或目录名加载。
(4) 抛出 “not found”
以上就是require语句的整个执行过程。那么require(‘react-native’);请求的到底是什么呢,其实就是node_modules\react-native\Libraries\react-native\react-native.js这个文件,该文件中导出了一些常用的,其源码如下
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
'use strict';
// Export React, plus some native additions.
// The use of Object.create/assign is to work around a Flow bug (#6560135).
// Once that is fixed, change this back to
var ReactNative = {...require('React'), /* additions */}
var ReactNative = Object.assign(Object.create(require('React')), {
// Components
ActivityIndicator: require('ActivityIndicatorIOS'),
DatePickerIOS: require('DatePickerIOS'),
DrawerLayout: require('DrawerLayoutAndroid'),
Image: require('Image'),
ListView: require('ListView'),
MapView: require('MapView'),
Modal: require('Modal'),
Navigator: require('Navigator'),
NavigatorIOS: require('NavigatorIOS'),
PickerIOS: require('PickerIOS'),
ProgressBarAndroid: require('ProgressBarAndroid'),
ProgressViewIOS: require('ProgressViewIOS'),
ScrollView: require('ScrollView'),
SegmentedControlIOS: require('SegmentedControlIOS'),
SliderIOS: require('SliderIOS'),
SnapshotViewIOS: require('SnapshotViewIOS'),
Switch: require('Switch'),
SwitchAndroid: require('SwitchAndroid'),
SwitchIOS: require('SwitchIOS'),
TabBarIOS: require('TabBarIOS'),
Text: require('Text'),
TextInput: require('TextInput'),
ToastAndroid: require('ToastAndroid'),
ToolbarAndroid: require('ToolbarAndroid'),
TouchableHighlight: require('TouchableHighlight'),
TouchableNativeFeedback: require('TouchableNativeFeedback'),
TouchableOpacity: require('TouchableOpacity'),
TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
View: require('View'),
ViewPagerAndroid: require('ViewPagerAndroid'),
WebView: require('WebView'),
ActionSheetIOS: require('ActionSheetIOS'),
AdSupportIOS: require('AdSupportIOS'),
AlertIOS: require('AlertIOS'),
Animated: require('Animated'),
AppRegistry: require('AppRegistry'),
AppStateIOS: require('AppStateIOS'),
AsyncStorage: require('AsyncStorage'),
BackAndroid: require('BackAndroid'),
CameraRoll: require('CameraRoll'),
Dimensions: require('Dimensions'),
Easing: require('Easing'),
ImagePickerIOS: require('ImagePickerIOS'),
InteractionManager: require('InteractionManager'),
LayoutAnimation: require('LayoutAnimation'),
LinkingIOS: require('LinkingIOS'),
NetInfo: require('NetInfo'),
PanResponder: require('PanResponder'),
PixelRatio: require('PixelRatio'),
PushNotificationIOS: require('PushNotificationIOS'),
Settings: require('Settings'),
StatusBarIOS: require('StatusBarIOS'),
StyleSheet: require('StyleSheet'),
VibrationIOS: require('VibrationIOS'),
// Plugins
DeviceEventEmitter: require('RCTDeviceEventEmitter'),
NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
NativeModules: require('NativeModules'),
Platform: require('Platform'),
processColor: require('processColor'),
requireNativeComponent: require('requireNativeComponent'),
// Prop Types
EdgeInsetsPropType: require('EdgeInsetsPropType'),
PointPropType: require('PointPropType'),
// See http://facebook.github.io/react/docs/addons.html
LinkedStateMixin: require('LinkedStateMixin'),
Perf: undefined,
PureRenderMixin: require('ReactComponentWithPureRenderMixin'),
TestModule: require('NativeModules').TestModule,
TestUtils: undefined,
batchedUpdates: require('ReactUpdates').batchedUpdates,
cloneWithProps: require('cloneWithProps'),
createFragment: require('ReactFragment').create,
update: require('update'),
if (__DEV__) {
ReactNative.addons.Perf = require('ReactDefaultPerf');
ReactNative.addons.TestUtils = require('ReactTestUtils');
module.exports = ReactN
了解了这个知识点后,我们来自定义一个模块,去使用原生的模块。假设有这么一个需求,我们需要使用Andorid中的Log类,但是React Native并没有为我们进行封装,那么我们自己动手实现一下吧。
我们需要继承ReactContextBaseJavaModule这个抽象类,重写getName()函数,用于返回一个字符串,这个字符串在端标记这个模块,暴露一个函数给javascript端,并且使用注解@ReactMethod进行标记,该函数的返回值必须为void,React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。
我们需要实现一个类实现ReactPackage接口,该接口中有三个抽象函数待实现,分别是createNativeModules,createJSModules,createViewManagers,这三个函数中,我们需要实现的最关键的函数就是createNativeModules,在该函数中我们需要添加前一步创建的ReactContextBaseJavaModule子类
构建ReactInstanceManager的实例时,通过调用 addPackage()函数,将上一步实现的ReactPackage添加进去。
接下来我们来实现代码。为了简单方便,这里只演示Log类中的d方法,即Log.d(String tag,String msg)
第一步,继承ReactContextBaseJavaModule类,重写getName()方法,因为是Log模块,所以直接返回字符串Log,暴露一个d方法给javascript端,返回值为void,只用注解进行标记。最终的代码如下。
public class LogModule extends ReactContextBaseJavaModule{
private static final String MODULE_NAME="Log";
public LogModule(ReactApplicationContext reactContext) {
super(reactContext);
public String getName() {
return MODULE_NAME;
@ReactMethod
public void d(String tag,String msg){
Log.d(tag,msg);
第二步,实现ReactPackage接口,在createNativeModules函数中添加我们的日志模块。其余两个函数返回空的List即可。
createNativeModules(ReactApplicationContext reactContext) {
List modules=new ArrayList();
modules.add(new LogModule(reactContext));
public List<Class> createJSModules() {
return Collections.emptyList();
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
" data-snippet-id="ext.9f07b33a14eabf3cbd00c1fb0b5009f1" data-snippet-saved="false" data-csrftoken="4TcSqJMN-qwxVPXurbJ8DIBEzePIUnitFBt4" data-codota-status="done">public class AppReactPackage implements ReactPackage {
public List createNativeModules(ReactApplicationContext reactContext) {
List modules=new ArrayList();
modules.add(new LogModule(reactContext));
public List<Class> createJSModules() {
return Collections.emptyList();
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
第三步,添加AppReactPackage 到ReactInstanceManager的实例中去,在我们的MainActivity中可以看到这么一段代码
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
我们再build函数之前调用addPackage进行添加即可,最终代码如下。
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(new AppReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
可以看到我们增加了一行.addPackage(new AppReactPackage())
这样,在Java端我们要做的就做完了,接下来就是javascript端了,这时候编译一下apk重新后运行,接下来我们来编写javascript端。
如果你不嫌麻烦,每次都要从NativeModules来访问我们的Log,那么现在你可以直接在javascript中进行访问了。就像这样子。
var React = require('react-native');
NativeModules,
var Log1= NativeModules.L
Log1.d("Log1","LOG");
但是,假如我再增加一个需求,就是当Log类在java层打印出一个日志的之后,希望在js端也输出以下这个日志,那么你会怎么做呢,或许你会说,这个简单,我再输出一下js的日志就ok了。就像这样子。
var React = require('react-native');
NativeModules,
var Log1= NativeModules.L
Log1.d("Log1","LOG");
console("Log1","LOG");
没错是没错,就是看着蛋疼,不好维护不说,通样的代码你得写多少遍。
这时候,我们就有必要封装一下javascript端的代码了,在index.android.js文件同目录下新建一个log.js,输入如下代码。
'use strict';
var { NativeModules } = require('react-native');
var RCTLog= NativeModules.L
var Log = {
d: function (
tag: string,
msg: string
console.log(tag,msg);
RCTLog.d(tag, msg);
module.exports = L
代码很简单,我们通过NativeModules拿到我们的Log模块在本地的实现,赋值给变量RCTLog,并且还声明了一个Log变量,里面有一个函数d,调用了RCTLog的d函数,并且在调用前输出了javascript端的日志。最后使用module.exports=Log将Log变量导出
接下来就是引用log.js文件了,看过上面的require语句的解析,这对你应该不成问题了。
var Log=require('./log');
Log.d("TAG","111");
这还没完,我们再提一个需求,就是我们希望这个Log模块能够提供一个常量,也就是TAG供我们使用,而这个常量定义在java层,以便以后我们使用的时候如果不想输入TAG,可以直接使用这个默认的TAG,就像这样子
var Log=require('./log');
Log.d(Log.TAG,"111");
那么这个要怎么实现呢,很显然,我们需要在log.js中加入这个变量,就像这样子
'use strict';
var { NativeModules } = require('react-native');
var RCTLog= NativeModules.L
var Log = {
TAG: RCTLog.TAG,
d: function (
tag: string,
msg: string
console.log(tag,msg);
RCTLog.d(tag, msg);
module.exports = L
这样虽然我们可以使用Log.TAG返回到这个值了,由于我们java层没有定义TAG,所以这时候会报错。因此我们需要在java层返回这个值,这又要怎么做呢,别急。我们重新回过头来看看我们实现的类LogModule,我们继续在该类中定义两个常量
private static final String TAG_KEY = "TAG";
private static final String TAG_VALUE = "LogModule";
什么用呢,看常量名字就是到了,key和value,键值对,我们希望通过TAG_KEY拿到TAG_VALUE ,也就是我们日志要用到的TAG,怎么实现呢。重写getConstants函数即可。
getConstants() {
final Map constants = MapBuilder.newHashMap();
constants.put(TAG_KEY, TAG_VALUE);
" data-snippet-id="ext.fffc26b61d8" data-snippet-saved="false" data-csrftoken="8CPAguR7-Ms31GPk3usjjppD11aB6bcq_vDo" data-codota-status="done">
public Map getConstants() {
final Map constants = MapBuilder.newHashMap();
constants.put(TAG_KEY, TAG_VALUE);
这时候重写编译运行一下,你就可以在javascript层通过Log.TAG就可以访问到对应的值了,值为LogModule,而为什么是Log.TAG而不是其他的值呢,因为我们constants中put进去的键就是TAG。
那么这个有什么作用呢,还记得android中我们的Toast的使用,显示时间有两个值吗,一个是Toast.LENGTH_SHORT,另一个是Toast.LENGTH_LONG,我们希望在javascript层通用有这么两个常量可以使用,那么就可以使用这种方法。我们可以看看的ToastAndroid的实现。
首先看java层
getConstants() {
final Map constants = MapBuilder.newHashMap();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
" data-snippet-id="ext.b6ffc6dafd1abab455a3" data-snippet-saved="false" data-csrftoken="Ws1sIYY7-0-swsiDcqXaHz8nq3x0PdVnFdw4" data-codota-status="done">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);
public String getName() {
return "ToastAndroid";
public Map getConstants() {
final Map constants = MapBuilder.newHashMap();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
看到没有,在getConstants函数中暴露了两个值给javascript层,SHORT对应java层的Toast.LENGTH_SHORT,LONG对应java层的Toast.LENGTH_LONG。接着看javascript层的代码
'use strict';
var RCTToastAndroid = require('NativeModules').ToastA
var ToastAndroid = {
SHORT: RCTToastAndroid.SHORT,
LONG: RCTToastAndroid.LONG,
show: function (
message: string,
duration: number
RCTToastAndroid.show(message+"lizhangqu", duration);
module.exports = ToastA
直接可以通过定义的变量SHORT或者LONG访问到,最终我们的使用就是这样子的。
var React = require('react-native');
ToastAndroid
ToastAndroid.show("toast",ToastAndroid.SHORT);
这还没完,这仅仅是没有返回值的情况,假如有返回值情况又是怎么样呢,比如javascript调用java层的方法,但是java层需要将结果返回javascript,没错,答案就是回调!,最典型的一个场景就是javascript层调用java层的网络请求方法,java层拿到网络数据后需要将结果返回给javascript层。通用的,我们用最快的速度实现一下这个模块。
继承ReactContextBaseJavaModule,实现getName方法,返回值为Net,暴露一个getResult方法给javascript,并进行注解,注意这个函数有一个Callback类型的入参,返回结果就是通过这个进行回调
public class NetModule extends ReactContextBaseJavaModule {
private static final String MODULE_NAME="Net";
public NetModule(ReactApplicationContext reactContext) {
super(reactContext);
public String getName() {
return MODULE_NAME;
@ReactMethod
public void getResult(String url,final Callback callback){
Log.e("TAG","正在请求数据");
new Thread(new Runnable() {
public void run() {
String result="这是结果";
Thread.sleep(1000);//模拟网络请求
callback.invoke(true,result);
} catch (Exception e) {
e.printStackTrace();
}).start();
Callback的定义如下,它是一个接口,invoke函数的入参是个数是任意的。
public interface Callback {
* Schedule javascript function execution represented by this {@link Callback} instance
* @param args arguments passed to javascript callback method via bridge
public void invoke(Object... args);
在前面的AppReactPackage类createNativeModules函数中注册该模块
modules.add(new NetModule(reactContext));
之后新建一个net.js文件,实现javascript层
'use strict';
var { NativeModules } = require('react-native');
var RCTNet= NativeModules.N
var Net = {
getResult: function (
url: string,
callback:Function,
RCTNet.getResult(url,callback);
module.exports = N
console.log("callback",code,result);
);" data-snippet-id="ext.107e880f3149ba75eadfdf8a2d0708f1" data-snippet-saved="false" data-csrftoken="j4Ei9vvD-5rn_ieDw5bD-7eRkjmp4yKJgSW8" data-codota-status="done">var Net=require('./net');
Net.getResult(
(code,result)=>{
console.log("callback",code,result);
如果不出意外,在java层将输出日志
11-20 22:30:53.598 /com.awesomeproject E/TAG: 正在请求数据
在javascript层,控制台将输出
callback true 这是结果
以上就是回调的一个示例,你可以简单想象成java层的网络请求模型,主线程开启子线程请求数据,子线程拿到数据后回调对应的方法使用handler通知主线程返回结果。
基本上,掌握了上面的内容,对原始模块的使用也差不多了,本篇文章是基于官方文档的最佳实践Native Modules,但是该文档坑太多,还需谨慎参考。该文档中【发送事件给javascript】段并没有进行实践实践,基本上原理一致,等有空了再研究研究。
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'这是深入浅出React Native的第三篇文章。
将index.ios.js中的代码全部删掉,为什么要删掉呢?因为我们准备从零开始写一个应用~学习技术最好的方式就是自己动手写,看别人的代码一百遍的效果也不如自己写一遍来的效果大~
我们要做的事情主要分成以下两步:
1. 创建组件
2. 将创建好的组件显示在app上
打开index.ios.js文件,输入
var HelloWorld = React.createClass({
render: function () {
Hello World
查找React文档可以看到该createClass的描述
ReactClass createClass(object specification)
通过传入一个描述说明(specification)来创建一个组件类,创建的这个组件类必须实现render这个方法,并且render方法只能返回一个节点,当然该节点可以包含任意多的子节点。
像上面我们创建了一个HelloWorld的组件类,该类实现了render这个方法,该方法只返回&View&这个子节点,当然,在&View&这个节点下还包含了&Text&这个节点。
第一步就完成了,你看,就是这么简单~
下面我们开始第二步。
AppRegistry.registerComponent('AwesomeProject', function() {
return HelloW
AppRegistry是运行React Native的应用程序的一个入口,一个应用程序的根组件必须通过调用AppRegistry.registerComponent方法将自己注册到应用中,这样原生系统才能正确加载并通过调用AppRegistry.runApplication来运行应用程序。
在我们的项目中,根组件就是我们的HelloWorld,至于第一个参数为什么是AwesomeProject,参看api可以看到
static registerComponent(appKey: string, getComponentFunc: ComponentProvider)
&第一个参数指的是appKey,如果大家还记得的话,我们通过react native命令行生成的项目模板的名字就叫做Awesome~当然这个名字可以改的,至于怎么改,我们以后再说,现在先保持这个名字。
这两件事情做完以后,切换回模拟器,CMD+R刷新(注意,服务器要保持运行状态哦~如果服务器停掉的话,npm start命令,还记得吗~不记得的话请翻看之前的教程哦),咦,怎么神马都没有~当然,也可能界面上出现一大片红色的错误信息~~
打开xcode可以看到其中有一段这样的错误:
系统找不到我们使用的React变量,所以报错了~其实不仅仅React我们没有定义,AppRegistry, View, Text我们都没有定义~所以下面我们要讲React Native定义的这些变量引入~
在index.ios.js文件的最上面添加下述代码~
var React = require('react-native');
var AppRegistry = React.AppR
var View = React.V
var Text = React.T
&因为AppRegistry, View, Text是React的一个属性,所以引入方式与React不同~
打开模拟器,CMD+R刷新下,就可以看到hello world显示在界面上啦~只是......位置有点点尴尬。
为了让这个显示在中间,我们为其添加一些style~
第一步需要引入StyleSheet变量。在引入变量的最后添加下面的代码:
var StyleSheet = React.StyleS
然后在AppRegistry.registerComponent方法的上面(其实位置无所谓,只需要在引入变量的下面就可以了,之所以写在registerComponent前面,只是为了代码看起来好看些~),添加
var styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center'
我们会在后续讲解StyleSheet,现在只需要知道,StyleSheet是一种样式的抽象,类似于CSS。在上述的style中,我们将container定义为水平、垂直居中。
修改HelloWorld组件,为其添加我们设置的样式。
var HelloWorld = React.createClass({
render: function () {
&View style={styles.container}&
Hello World
大功告成~在模拟器上刷新,就可以看到Hello World这几个字显示在iphone的中间啦~
阅读(...) 评论()react-native-viewpager是国人实现的第一个自定义的 React Native 的组件——ViewPager,纯 JavaScript 打造,自带页面指示器(也支持定义样式),支持自动播放,支持无限循环滑动,Android 和 iOS 上通用。项目地址在这里:也发布到了npm上了,直接 npm install react-native-viewpager 你就可以得到它。这个ViewPager组件类似ListView, 能毫无性能问题情况下渲染输出数百个页面,性能比Android的更好, ViewPager能自动播放,自动循环,无限滚页。[该贴被banq于 12:51修改过]
好棒,希望组件越来越多
最佳分辨率
OpenSource
Code & 2002-20

我要回帖

更多关于 react native 的文章

 

随机推荐