1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| 是在 JS 端编写 React 代码,通过 JavaScriptCore 引擎,把 JS 端编写的组件和事件转换成 Native 原生组件进行渲染
React 编写代码(跨端,热更新) -> JSCore引擎解析JS代码(内存消耗,性能消耗) ->映射成Native组件(Native性能)
ReactNative 启动是从创建一个 RCTRootView 作为入口视图容器开始运作的,创建 RCTRootView 时,会先创建一个 JSBridge 作为 Native 端与 JS 端交互的桥梁。整个 RN 的启动工作基本都是在创建 JSBridge 时做的。
JSBridge 的核心是 RCTBatchedBridge ,JSBridge 的工作主要在 RCTBatchedBridge 初始化中处理。启动流程采用 GCD 来进行多线程操作,其中大部分耗时操作是在并发队列com.facebook.react.RCTBridgeQueue中
RCTBatchedBridge 启动主要进行六个准备工作: 1.加载 JSBundle 代码(并行队列异步执行) 将 JavaScript 源码加载到内存中,方便之后注入和执行,这一步中,React 中的 JSX 语法已经转换成 JavaScript
2.初始化 Native Modules(同步执行) 同步初始化所有不能被懒加载的供 JS 端调用的 Native 模块 找到所有 Native 需要暴露给 JavaScript 的类(即被标记有宏:RCT_EXPORT_MODULE()的类),方便后面把这些模块信息注入 JS 端
3.初始化 JSCExecutor(与第4步在同一并行队列同时执行) JSCExecutor 是JavaScriptCore引擎,负责JS端和Native端的通信 初始化时,创建一个优先级跟主线程优先级同级的单独 JS 线程,同时创建一个 Runloop,让 JS 线程能循环执行不会退出 初始化时,通过 JavaScriptCore 作为引擎,创建 JS 执行的上下文环境,并向 JS 上下文中注入 JS 与 Native 通信的方法
4.创建 Module 配置表(与第3步在同一并行队列同时执行) Module 配置表: 把所有模块信息集中收集起来,保存到一个数组中,经过序列化后,注入到JS中。JS 端通过 Native端注入的 nativeRequireModuleConfig 方法,根据 module 名可以查询该模块配置信息 创建 Module 配置表,与初始化 JSCExecutor 的操作一起被加入并发队列
5.注入 Module 配置信息到 JSCExecutor(第3、4步执行完再执行) 当初始化 JSCExecutor 和创建 Module 配置表工作都准备好后,会将 module 模块配置信息注入 JS 端
6.执行 JSBundle 代码(前5步都执行完再执行) 以上五步操作都执行完成后,执行 JSBundle 中的 JavaScript 源码。至此,JavaScript 和 Objective-C 都具备了向对方交互的能力,启动流程的准备工作算是全部完成了。 启动完成之后,就会进入渲染层,渲染层分js层和native两部分
JS 层渲染 diff 算法 React 通过setState界面刷新时,并不会马上对所有真实的 DOM 节点进行操作,而是先通过 diff 算法计算。然后,再对有变化的 DOM 节点进行操作(native 是对原生 UI 层进行操作),具体刷新步骤如下: 1.state 变化,生成新的 Virtual Dom; 2.比较 Virtual Dom 与之前 Virtual Dom 的异同; 3.生成差异对象; 4.遍历差异对象并更新真实 DOM;
DOM 操作很耗时,使用 JS 对象来模拟 DOM Tree,在渲染更新时,先对 JS 对象进行操作,再批量将 JS 对象 Virtual Dom 渲染成 DOM Tree,从而减少对 DOM 的操作,提升性能
Virtual Dom 本质是用来模拟 DOM 的 JS 对象。一般含有标签名(tag)、属性(props)和子元素对象(children)三个属性
React Diff 算法相对于传统的 diff 算法,复杂度从 O(n^3)降到 O(n) React基于以下的两个假设,减少了不必要的计算: 1.两个相同组件将会生成相似的DOM结构,两个不同组件将会生成不同的DOM结构。 2.对于同一层次的一组子节点,它们可以通过唯一的id进行区分。
对于假设 1: 两个相同组件,一般指的是相同的类,包含 React 官方定义的组件(View,Text)和程序员自定义的组件(这也是React 组件化开发的一个原因,可以提升 diff 算法的效率); 对于假设 2: 一般指的是使用map遍历生成的列表视图或者使用ListView/FlatList等列表组件;
相同类型节点的比较: 由于新旧节点类型相同,DOM 结构没有发生变化,仅对属性(style)进行重设从而实现节点的转换和界面的更新,这种情况,通过这类diff算法计算后,会调用 Native的 updateView 来刷新界面
不同节点类型的比较: 首先抽象成 DOM tree 节点模型,然后从父节点到子节点意义对比,最后确定需要更新的节点最小单位 通过这类diff算法计算后,会调用 Native的 manageChildren 来刷新界面
列表节点的比较: 在渲染列表节点时,它们一般都有相同的结构,只是内容有些不同而已,常见的,如使用map遍历生成的列表视图或者ListView/FlatList等列表组件,如果开发的时候没有写 key,编译器会给出警告提示 通过唯一的 key 进行区分,通过给每个节点添加唯一的 key,可以极大的简化 diff 算法,减少对 DOM 的操作。列表节点的比较主要有添加节点、删除节点、节点排序三种场景,js层diff算法计算后,会调用 Native的 manageChildren 来刷新界面
AppRegistry 而在React Native 中,AppRegistry是RN应用的入口函数。 AppRegistry负责注册运行React Native应用程序的JaveScript入口,程序入口组件使用AppRegistry.registerComponent来注册。当注册完应用程序组件后,Native系统(OC)就会加载jsbundle文件并触发AppRegistry.runApplication运行应用
|