React native与原生交互
React native提供了一种跨平台的开发方式,在大部分的界面开发、业务逻辑开发上都可以直接使用React Native跨平台开发,写一套代码在不同的环境中运行。但是我们仍然有很多需要用到原生的地方,或者在某些地方使用原生更加优化。比如处理多线程、数据库、网络接口这一类。或者部分老的项目,在原生层面已经针对数据库、网络接口、图片处理等做了相对较优化的封装,我们就不需要在React native中再重新写一遍这些功能,可以直接复用原生的这些已经封装好了的功能。
React Native中文官网关于React native与原生的交互在这个链接中,大家可以自行查看:https://www.react-native.cn/docs/native-modules-intro
其中讲到有两种方式,可以在React Native项目中编写原生代码,并且调用,一种是把原生代码做成一个npm包,然后再引用,一种是直接在React Native工程中直接编写原生代码调用。
我们一般使用后者,直接在工程中编写原生代码。至于打包成npm包大家可以自己研究一下。
1、实战开发流程:
React Native代码调用原生代码流程如下:
安卓流程
1、编写原生的功能类,需要继承自ReactContextBaseJavaModule
类中可以编写和调用封装自己原生的功能,将需要导出给React Native使用的方法,使用注解@ReactMethod。方法的返回类型必须为void。
另外ReactContextBaseJavaModule类要求派生类要实现一个方法,getName方法。这个方法会返回一个字符串,该字符串在React Native中被用来标记这个原生的模块名。也就是说,在React Native
2、写一个注册类,继承自ReactPackage中,在其中注册第一步中写的功能类
3、在MainApplication.java文件的getPackages中调用注册类注册
4、完成以上3步,就可以在React Native中通过nativeModule调用原生方法了
iOS流程
1、编写原生的功能类,需要实现RCTBridgeModule协议,类中包含包含RCT_EXPORT_MODULE()宏用来指定React Native中可以访问的类名。另外通过RCT_EXPORT_METHOD()宏来声明哪些方法是在ReactNative中可以调用的
2、直接在React Native中调用就可以了。
2、调用原理
上面就是React Native调用原生的实践步骤,根据安卓的步骤可以看出来,整个过程就是在原生中编写好自己的原生代码,开放相关接口给React Native,然后就有很重要的一步,就是将这个原生的模块注册到React Native桥接模块中,注册后,在React Native中调用桥接的时候,就可以直接使用名称访问注册模块和方法,调用到原生开发的代码和功能。
这里需要画一个图说明,Native 、RNBridge、RN之间的关系和流程
到了这里可能会有人文,为什么android需要将原生中写的模块注册才能被React Native调用,但是iOS中确没有注册这一步,其实iOS也有,在一篇博客中讲的很清楚,中文地址:https://blog.csdn.net/future_challenger/article/details/56979067,英文地址https://tadeuzagallo.com/blog/react-native-bridge/。大家可以自行查阅,在这里我也带着大家梳理一遍
首先iOS的原生类中,有两个很重要的宏RCT_EXPORT_MODULE()和RCT_EXPORT_METHOD()上面也说到了,其中RCT_EXPORT_MODULE()宏就是返回模块名称的,看起来跟安卓重写的方法getName功能类似,但是这里不一样,我们可以看一下这个源码
这里做了三件事情
* 首先它声明了RCTRegisterModule为extern方法,也就是说这个方法的实现在编译的时候不可知,而在link的时候才可知。
* 声明了一个方法moduleName,这个方法返回可选的宏定义参数js_name,一般是你希望有一个专门的模块名称,而不是默认的ObjC类名的时候使用。
* 最后,声明了一个load方法(当app被加载进内存的时候,load方法也会被调用)。在这个方法里调用RCTRegisterModule方法来让RN的bridge感知到这个模块。
然后运行时会调用load方法,将所有原生的模块、方法都注册到RNBridge桥接模块中。这样就完成了注册的流程,后面跟安卓都一样。所以总结一下iOS也有注册流程,就是在RCT_EXPORT_MODULE宏中定义了注册load方法,并且使用运行时机制动态加载注册到RNBridge桥接模块中,供RN调用。
3、参数互通
既然能相互调用就会涉及参数的相互传递,这里可以参考如下:
iOS:
string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array (NSArray) 可包含本列表中任意类型
object (NSDictionary) 可包含 string 类型的键和本列表中任意类型的值
function (RCTResponseSenderBlock)
android:
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
4、原生如何回调RN
说到这里,只能RN调用原生,那么如果RN调用了原生,需要原生给一个回调时如何处理,比如RN想要获取原生目前的暗黑状态或者屏幕宽高,那么这些数据如何回调给RN呢。
5、Promise方法调用
除了上述讲的桥接+callback的方法访问RN调用原生外,还有一种Promise方法调用,参考如下:https://www.jianshu.com/p/41dd77a83c13
实现步骤
6、callback和promise的区别
参考如下https://blog.csdn.net/codetomylaw/article/details/51927126
http://www.atguigu.com/hyrd/16686.html
callback就是在RN中写一个回调方法,等着原生代码执行完成之后回调。
优点:比较容易理解;
缺点:1.高耦合,维护困难,回调地狱;2.每个任务只能指定一个回调函数;3.如果几个异步操作之间并没有顺序之分,同样也要等待上一个操作执行结束再进行下一个操作。
2.Promise
ES6给我们提供了一个原生的构造函数Promise,Promise代表了一个异步操作,可以将异步对象和回调函数脱离开来,通过.then方法在这个异步操作上绑定回调函数,Promise可以让我们通过链式调用的方法去解决回调嵌套的问题,而且由于promise.all这样的方法存在,可以让同时执行多个操作变得简单。
promise对象存在三种状态:
1)Fulfilled:成功状态
2)Rejected:失败状态
3)Pending:既不是成功也不是失败状态,可以理解为进行中状态
Promise的缺点:
1.当处于未完成状态时,无法确定目前处于哪一阶段。
2.如果不设置回调函数,Promise内部的错误不会反映到外部。
3.无法取消Promise,一旦新建它就会立即执行,无法中途取消。
7、RCTDeviceEventEmitter事件通知的方式
刚才讲的都是
8、相互调用加载界面,ios加载RN时也能传递参数