导读:
- 来源:React Native是Facebook在2013年启动的一个内部项目,2015年在F8大会上发布的,这个项目目前在GitHub已经几万的star和上万的fork了(https://github.com/facebook/react-native)。
- 解决的问题:设计初衷是成为 JavaScript 和原生应用之间的一个异步,序列化和批处理的“桥梁”,慢慢发展到后面的一个跨平台移动APP开发框架,提供了一种多平台同时运行的语言环境,使你能够在JavaScript 和react的基础上获得完全一致的开发体验。
- 初步对比:【RN】 JS引擎,RN在iOS使用系统自带的JSCore,安卓上是外部引入的JSCore,渲染使react,理念“Learn Once ,Write Anywhere” ,RN 并做不到一份代码很好的适配 iOS 和安卓的所有机型和系统,需要你根据具体情况去做适配; 【Weex】中iOS也用系统自带的JSCore,安卓中使用V8,渲染使用的vue;理念“Write Once Run Everywhere”;两者最后的渲染都是纯native控件完成。
官网:http://facebook.github.io/react-native
中文官网:https://reactnative.cn
基本原理:
应用架构
以iOS为例
- OC 调用 JS
- 即使使用了 React Native,我们依然需要 UIKit 等框架,调用的是 Objective-C 代码。总之,JavaScript 只是辅助,它只是提供了配置信息和逻辑的处理结果。React Native 与 Hybrid 完全没有关系,它只不过是以 JavaScript 的形式告诉 Objective-C 该执行什么代码。
- 苹果提供了一个叫做 JavaScript Core 的框架,这是一个 JavaScript 引擎。通过下面这段代码可以简单的感受一下 Objective-C 如何调用 JavaScript 代码
JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];
这里的 JSContext指的是 JavaScript 代码的运行环境,通过 evaluateScript即可执行 JavaScript 代码并获取返回结果。
- JavaScript 是一种脚本语言,它不会经过编译、链接等操作,而是在运行时才动态的进行词法、语法分析,生成抽象语法树(AST)和字节码,然后由解释器负责执行或者使用 JIT 将字节码转化为机器码再执行。整个流程由 JavaScript 引擎负责完成。
- JavaScript 是一种单线程的语言,它不具备自运行的能力,因此总是被动调用。很多介绍 React Native 的文章都会提到 “JavaScript 线程” 的概念,实际上,它表示的是 Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行。
- JS 调用 OC
- 由于 JavaScript Core 是一个面向 Objective-C 的框架,在 Objective-C 这一端,我们对 JavaScript 上下文知根知底,可以很容易的获取到对象,方法等各种信息,当然也包括调用 JavaScript 函数,真正复杂的问题在于,JavaScript 不知道 Objective-C 有哪些方法可以调用。
- React Native 解决这个问题的方案是在 Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。这样,无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId、MethodId和 Arguments这三个元素,它们分别表示类、方法和方法参数,当 Objective-C 接收到这三个值后,就可以通过 runtime 唯一确定要调用的是哪个函数,然后调用这个函数。
- OC 与 JS 函数互相回调实现
- 对于 Objective-C 来说,执行完 JavaScript 代码再执行 Objective-C 回调毫无难度,难点依然在于 JavaScript 代码调用 Objective-C 之后,如何在 Objective-C 的代码中,回调执行 JavaScript 代码。
- React Native 的做法是:在 JavaScript 调用 Objective-C 代码时,注册要回调的 Block,并且把 BlockId 作为参数发送给 Objective-C,Objective-C 收到参数时会创建 Block,调用完 Objective-C 函数后就会执行这个刚刚创建的 Block。
以下一个图例描述下 RN回调OC的过程
OC暴露给JS方法
- 这一步在方法 initModulesWithDispatchGroup: 中实现,主要任务是找到所有需要暴露给 JavaScript 的类。每一个需要暴露给 JavaScript 的类(也成为 Module,以下不作区分)都会标记一个宏:RCT_EXPORT_MODULE,这个宏的具体实现并不复杂
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
这个类在 load 方法中就会调用 RCTRegisterModule 方法注册自己:
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
[RCTModuleClasses addObject:moduleClass];
}
因此,React Native 可以通过 RCTModuleClasses 拿到所有暴露给 JavaScript 的类。下一步操作是遍历这个数组,然后生成 RCTModuleData 对象
for (Class moduleClass in RCTGetModuleClasses()) {
RCTModuleData *moduleData = [[RCTModuleData alloc]initWithModuleClass:moduleClass bridge:self];
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
RCTModuleData 对象是模块配置表的主要组成部分。如果把模块配置表想象成一个数组,那么每一个元素就是一个 RCTModuleData 对象;
这个对象保存了 Module 的名字,常量等基本信息,最重要的属性是一个数组,保存了所有需要暴露给 JavaScript 的方法;
- 不是所有方法都需要暴露出来,暴露给 JavaScript 的方法需要用 RCT_EXPORT_METHOD 这个宏来标记,它为函数名加上了 __rct_export__ 前缀,再通过 runtime 获取类的函数列表,找出其中带有指定前缀的方法并放入数组中,
- (NSArray> *)methods
{
if (!_methods) {
NSMutableArray> *moduleMethods = [NSMutableArray new];
if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
[moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
}
unsigned int methodCount;
Class cls = _moduleClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[I];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector);
id moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
}
}
free(methods);
cls = class_getSuperclass(cls);
}
_methods = [moduleMethods copy];
}
return _methods;
}
因此 Objective-C 管理模块配置表的逻辑是:Bridge 持有一个数组,数组中保存了所有的模块的 RCTModuleData 对象。只要给定 ModuleId 和 MethodId 就可以唯一确定要调用的方法。