RN 杂谈(一)

导读:

  • 来源: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

基本原理:

应用架构

RN 杂谈(一)_第1张图片
1531755416006-ba10345c-9402-452f-9ec4-66255f4db269.jpeg

以iOS为例

  1. 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 代码只会在这个线程中执行。
  1. 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 唯一确定要调用的是哪个函数,然后调用这个函数。
  1. 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的过程
RN 杂谈(一)_第2张图片
1531848751155-f3cce384-82b2-4742-8ed7-0081c9a04842.png

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 就可以唯一确定要调用的方法。

你可能感兴趣的:(RN 杂谈(一))