react-native 原理

109 阅读6分钟

前言

先演示一段官方最简单的demo

RCTRootView 入口

  • RCTRootView是RN应用承载页面的容器,分析就从RCTRootView开始

  • image.png

  • 在需要使用到RN的页面中使用RCTRootView 初始化一个实例,入参是一个RCTBridge和moduleName

JSBridge 初始化

image.png

  • RCTBridge通过传入JSBundle的地址进行初始化,会进入一系列的setup工作

在setUp中调用start方法

image.png

在start方法中调用 excuteSourceCode 方法

image.png

在excuteSourceCode中调用loadScriptFromString方法

image.png

执行到 instance的 loadBundle方法

image.png

image.png

执行到NativeToJsBridge中的loadBundle方法

image.png

继续执行到 JSIExecutor的方法

image.png

最终执行到JSCRuntime:evaluateJavaScript 方法,内部调用了 JavaScriptCore::JSEvaluateScript 方法。

image.png

整个加载JSBundle 的流程是这样子:

image.png

  • 结束了解析执行JSBundle后,并执行目录下index.js,这里先不管它是如何执行到 JSBundle 中的index.js,只要知道这个index.js 是RN js 侧工程根目录底下的唯一一个叫 index.js文件,也称为入口文件

image.png

image.png

JS侧初始化

image.png

bundle 加载成功之后,会走到bundleFinishedLoading方法,接着执行 runApplication 方法,往bridge中传递module为AppRegistry,method为runApplication的 js 调用,目前暂时先不讨论如何实现的

image.png

  • Bridge会通过这个参数调用到对应js的方法,并查找出runables数组中的组件并且调用run方法

  • 还记得上面 解析执行完JSBundle 会执行 index.js 中的 registerComponenet 方法么

image.png

  • component会将自身注册进runables数组中,为了后续运行

  • run 方法执行 renderApplication,传入componentProvider,即是index.js 中注册的 app() 首页组件

  • renderApplication 传入的 RootComponent 参数,也是 外层componentProvider包裹了 性能检测logger形成的组件

image.png

最终形成renderable 组件传递给Renderer/shinms/ReactNative类的 render方法

image.png

image.png

updateContainer->scheduleUpdateOnFiber->renderRootSync->workLoopSync->completeUnitOfWork->completeWork->createInstance

经过较长的调用链之后来到了createInstance的方法,最终会调用原生的UIManager.createView的桥接方法

image.png

这里回到原生代码 createView 方法

image.png

原生的类如何提供给JS调用

  • 这里切换到原生的类如何提供出来给JS调用

  • 所有需要给js调用的类都会使用 RCT_EXPORT_MODULE()宏定义 image.png

  • iOS会在启动加载二进制时调用load方法 image.png

  • 所有使用RCT_EXPORT_MODULE()标识的类,会在启动的时候将自身加入到RCTModuleClasses中 image.png

同样导出方法会使用RCT_EXPORT_METHOD宏定义导出

image.png

宏替换经过层层嵌套最终是这样子: image.png

以上的宏替换会被替换为:

+(const RCTMethodInfo *)__rct_export__(__LINE__, __COUNTER__) {
    static RCTMethodInfo config = {"", "test:(NSString *)name", NO}; 
    return &config; 
}

(LINE, COUNTER) 这里是当前的代码行数(__LINE__)加上预编译的次数(__COUNTER__), __COUNTER__ 是一个计数器,每次执行都会加1

eg.

+(const RCTMethodInfo *)__rct_export__9424 {
    static RCTMethodInfo config = {"", "test:(NSString *)name", NO}; 
    return &config; 
}

RN会遍历所有以__rct_export__为前缀的方法并执行以导出曝露给 JS 的接口,细节在后续会讲解 image.png

已经大致知道了类导出和方法导出,我们回到刚才的视图创建:

image.png

这里使用了RCTShadowView,RCTShadowView是一个布局的虚拟视图,内部使用yoga进行布局,yoga是一个布局工具,不同于iOS自动布局AutoLayout

接下来使用setProps:对shadowView 进行属性设置,这里又要讲到区别类导出,方法导出的第三种导出:属性导出, 同样属性导出也使用宏替换:

image.png

image.png

eg.

+ (NSArray<NSString *> *)propConfig_backgroundColor  { 
    return @[@"UIColor"]; 
}

最终通过遍历所有propConfig开头的方法作为方法选择子,将json转为原生相应的类型并通过方法调用设置值 image.png

image.png

image.png

完成了视图的初始化,需要将视图加入到视图树中,同样到这里会调用原生方法UIManager.setChildren方法 image.png

调用来到原生端:

image.png

  • 就如APP.js 中这个视图结构
  • 所有遵循RCTComponent协议的视图组件,会按照树形结构将视图加入到yoga视图树进行布局

image.png

image.png

最终页面呈现出来是这样:

image.png

  • 因为布局用的是yoga,所以没有像AutoLayout一样的约束线条

JS 和 native 通讯

获取RCTModuleClasses数组

image.png

还记得启动的时候会将通过EXPORT_MODULE宏定义导出的类注册进RCTModuleClasses数组,会在bridge初始化之后,通过_initializeModules->_registerModulesForClasses:,增加一些必要的参数,糅合成RCTModuleData结构的数组成为NativeModules

生成Instance

  • 在初始化RCTCxxBridge时,start->_initializeBridge->_initializeBridgeLocked->_buildModuleRegistryUnlocked的调用链

image.png

会将原生的RCTModuleData结构的数组转成NativeModule结构的数组 image.png

包装进c++ ModuleRegistry类中 image.png

并且实例化出c++ Instance类 image.png

Native调用JS

Native 可以通过 这种方式对JS发起调用 enqueueJSCall()

image.png

image.png

Native调用JS可以使用RCTCxxBridge:enqueueJSCall:method:args: 来调用,内部调用了reactInstance的callJSFunction

image.png

nativeToJsBridge

nativeToJsBridge的初始化是在Instance初始化的时候调用initializeRuntime

image.png

这里会往js上下文中设置几个全局的属性nativeFlushQueueImmediate,nativeModuleProxy,nativeCallSyncHook,可以在js侧使用global.nativeFlushQueueImmediate来调用

image.png

同样在调用callFunction时,判断如果flushQueue不存在的时候,会去执行bindBridge() image.png

bindBridge中会获取js上下文的全局属性callFunctionReturnFlushedQueue函数,这是在js侧声明的属性方法 image.png

调用callFunctionReturnFlushedQueue_->call,最终会调用到JavaScriptCore::JSObjectCallAsFunction方法,并且调用到MessageQueue.js的调用callFunctionReturnFlushedQueue->__callFunction()调用方法

image.png

流程图

image.png

JS调用Native

JS 可以通过 这种方式对Native发起调用 NativeModules.customModule.customFunction()

image.png

NativeModules.js在初始化的时候会获取global.nativeModuleProxy image.png

还记得JSIExecutor 在初始化的时候,会在js上下文注入一个全局属性,nativeModuleProxy image.png

每次访问NativeModules.xxx,就会访问到getter方法 image.png

接着调用JSINativeModules::getModule,通过模块名,找到模块对象 image.png

createModule会获取js侧global中的__fbGenNativeModule并且执行call方法 image.png

image.png

即调用genModule方法 image.png

在内部调用genMethod方法,生成js侧方法,并且在promise中执行enqueueNativeCall image.png

BatchedBridge就是MessageQueue.js 的实例,在方法中,有两种模式,如果超过一定时间阈值会直接调用global.nativeFlushQueueImediate,否则会放入this.queue队列,等待原生调用 image.png

直接调用

先看看直接调用会是什么样子 image.png

同样是预先注入js 上下文中的原生属性,这时候会调用callNativeModules,通过moduleId,methodId,arguments,callId,最终通过原生的方法调用

image.png

image.png

image.png

image.png

原生拉取

再看看原生拉取是什么样子

  • 原生拉取是通过runloop的事件接收触发,手势,timer

image.png

到JSIExecutor::callFunction->JSContext.callFunctionReturnFlushedQueue_->call image.png

JS 会先根据 moduleId 和 methodId 完成对 JS 侧业务的调用,然后会执行 flushedQueue 函数将队列清空,再将队列数组返回给 Native 侧 image.png

Native 侧拿到队列就会继续执行 JSIExecutor->callNativeModules->ModuleRegistry

image.png

image.png

image.png

流程图

image.png

参数传递和回调

在调用enqueuNativeCall时,传递的方法是moduleID,methodID,onFail,onSuceess image.png

由于global.__fbGenNativeModule = genModule;原生调用__fbGenNativeModule传递ModuleConfig image.png

image.png

js侧触发genModule方法,这是ModuleConfig的信息 image.png

在调用是传递的参数即使ModuleConfig中对应的ID image.png

回调

会生成一个callbackID并使用map,将js的回调和callbackID关联 image.png

原生在触发调用时,NativeToJsBridge::callNativeModules -> ModuleRegistry::callNativeMethod -> RCTNativeModule::invoke -> MethodCallResult::invokeInner,会处理原生的方法签名,当发现回调是reposenSenderBlock时,会在内部调用主动调用 enqueueCallback 方法 image.png

原生通过传递回callbackID,以及参数,完成回调 image.png

流程图

image.png