有时候我们的项目
有时候我们的项目需要:
访问Android平台上的API时, 但是我们的React Native可能还没有相应的模块包装;
再或者我们需要复用一些Java代码的时候, 而不是用JavaScript重新实现时;
又或者我们需要实现一些高性能的, 多线程的(因为JavaScript只跑在一个线程上)的代码, 譬如: 操作数据库, 加载图片等等的时候;
这时候我们可以采用封装的方式, 将这些高级特性(平台上的API)封装成一个模块, 这样可以提高我们的工作效率, 减少许多代码量;
接下来我们来实现Android上的Toast模块, 为什么实现这个模块? 因为这个模块较为简单, 而且我们开发项目时也经常用到; 假设我们的希望从JavaScript发起一个Toast消息(React Native中内置了一个名为ToastAndroid的模块);
实现步骤:
1. 首先我们创建一个原生模块, 将这个模块命名为:ToastModule, 一个原生模块是集成了 ReactContextBaseJavaModule 的Java类, 它可以实现一些JavaScript所需的功能; 在这里我们的目标是在JavaScript的代码里, 当我们点击一个Button的时候, 会弹出一个类似Android里的Toast通知:
代码如下:
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); } @Nullable @Override 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; } @ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); } @Override public String getName() { return "ToastExample"; } }
> 注意: ReactContextBaseJavaModule 要求派生类实现 getName() 方法, 这个方法用来返回一个字符串(这个字符串很重要, 在后面的JavaScript代码里自定义原生模块的时候, 使用的就是该字符串, 在JavaScript端用来标记这个模块 ) ;
@Override public String getName() { //如果模块有RCT前缀,那么这个前缀会被自动清除, 所以返回的字符串如果为RCTToastExample, //那么在JavaScript端依然通过React.NativeModules.ToastExample访问到这个模块; return "ToastExample"; }
接下来, 有一个可选的方法getContants()方法, 用来返回需要导出给JavaScript使用的常量(这个方法不一定需要实现, 但是定义一些可以被JavaScript同步访问到的预定义的值时还是非常有用的)
@Nullable @Override public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SHORT", Toast.LENGTH_SHORT); constants.put("LONG", Toast.LENGTH_LONG); return constants; }
在我们自定义的原生模块中, 最后一步就是要导出一个方法给JavaScript使用, 这时候Java方法需要使用到@ReactMethod这个注解, 这个方法的返回值必须为null; 因为React Native的跨语言访问是异步进行的, 所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件;
@ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); }
2.注册模块
> 我们想要使用这个模块, 那么我们首先需要做的事情就是注册这个模块, 这时候我们需要在应用的Package类的 createNativeModules 方法中添加这个模块, 如果我们没注册这个模块, 那么我们无法在JavaScript中访问到这个模块:
public class AnExampleReactPackage implements ReactPackage { //这个方法为注册我们自定义的模块; @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new ToastModule(reactContext)); return modules; } //这个方法为注册我们的自定义的组件(以后的原生组件会讲到); @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
然后这个package需要在MainApplication.java文件中的getPackages()方法中提供, 这个时候我的demo中, 我自己创建了一个新的MyApplication, 继承了Application, 并且实现了ReactApplication, 只不过我们创建完这个Application后 ,我们需要在Android Manifest中的application节点中设置name: MyApplication, 不了解的可以先去看我第一篇文章《初识React Native(一)—集成到原生Android项目》 :
public class MyApplication extends Application implements ReactApplication { private final ReactNativeHost reactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList(new MainReactPackage(), new AnExampleReactPackage()); } }; ... }
3.运行我们的原生模块:
> 我们通常可以把原生模块封装成一个JavaScript模块, 这样我们从JavaScript端访问起来更加方便, 但是这不是必须的, 但是我依然封装成了JavaScript模块, 我的封装原生模块的ToastModule.js文件:
import { NativeModules } from 'react-native';export default NativeModules.ToastExample;
最后就是在我们React Native界面上使用这个模块了, 在这我生成一个Button, 当我们点击这个Button的时候, 就会弹出这个Toast通知:
...import ToastExample from './ToastModule';......export default class HelloReactNative extends React.Component { _onPressButton() { console.log("you tapped the button !"); ToastExample.show('clicked me', ToastExample.SHORT); } render() { return ( <View style={styles.container}> ... <TouchableNativeFeedback onPress={this._onPressButton}> <Text style={styles.button}>Button</Text> </TouchableNativeFeedback> ... </View> ) } }