为了更好的理解 React Native,我们需要了解 RN 的架构原理。这里主要介绍两个内容
- 现有架构
当前 RN 正在使用的架构 - 新架构
2018年6月,Facebook推出了 RN 的重构计划。我们需要了解下一代 RN 的架构原理。
现有架构
架构模型
基本架构模型如下:
- Native 是原生部分,例如:iOS 原生或 Android 原生
- JS 端主要是 React 语法
- Bridge 用与 Native 和 JS 的通信
- 因为 Native 和 JS 相对独立。彼此通信是通过桥接器(Bridge)来实现。
详细一点的架构模型
- 最上层提供类 React 支持,运行在 JSC 提供的 JavaScript 运行时环境中
- Bridge 层将 JavaScript 与 Native 世界连接起来。具体的,
Shadow Tree 用来定义 UI 效果及交互功能,
Native Modules 提供 Native 功能(比如蓝牙), - 二者之间通过 JSON 消息相互通信
线程模型
- JS 线程
- JS 代码的执行线程,将源码通过 Metro 打包后,传给 JS 引擎进行解析
- Main 线程(也称为 UI 线程或原生线程)
- 主要负责原生渲染(Native UI)和调用原生模块(Native Modules)
- Shadow 线程(也称为 Layout 线程)
- 创建 Shadow Tree 来模拟 React 结构树(类似虚拟 DOM)
- 再由 Yoga 引擎将 Flexbox 等样式,解析成原生平台的布局方式
RN使用 Flexbox 布局,但是原生是不支持,Yoga 用来将 Flexbox 布局转换为原生平台的布局方式。
渲染机制
- JS 线程将视图信息(结构、样式、属性等)传递给 Shadow 线程,
- 创建出用于布局计算的 Shadow Tree,Shadow 线程计算好布局之后,再将完整的视图信息(包括宽高、位置等)传递给主线程
- 主线程据此创建 Native View(UI)
也可以通过下图,来理解渲染过程:
线程间通信
现有架构启动流程
新架构
新旧架构对比
新架构的主要改动
- JavaScript 层:
- 支持 React 16+ 的新特征增强
- JS 静态类型检查(CodeGen)
- 引入 JSI,允许替换不同的 JavaScript 引擎。支持 JS 与 Native 直接通信
- Bridge 层:
- 划分成 Fabric 和 TurboModules 两部分,分别负责 UI 管理与 Native 模块
- Native 层:
- 精简核心模块,将非核心部分拆分出去,作为社区模块,独立更新维护
增强类型检查
- CodeGen 是 FaceBook 推出的代码生成工具
通过 CodeGen,自动将 Flow 或者 TypeScript 等有静态类型的 JS 代码翻译成 Fabric 和TurboModules 使用的接口文件。 - 加入类型约束后的作用:
- 减少了数据类型错误
- 减少了数据验证的次数,提高了通信性能
举个例子:JS 中的数字经常被引号引起来,从而将数字类型转成了字符串。将转换后的数字传递给 bridge 的时候,通常 iOS下会静默失败,而 Android 会崩溃。
另外。类型约束对通信性能也有一定提升。因为,在加入类型约束之前,每次通信都需要进行数据验证。加载类型约束之后,我们就没有必要每次通信都进行数据验证了。减少了数据验证的次数,就会提高通信性能。
JSI(JavaScript Interface)
不同于之前直接将 JavaScript 代码输入给 JSC,新的架构中引入了一层 JSI(JavaScript Interface),作为 JSC 之上的抽象
- JSI 是一个用C++写成的轻量级框架。其作用主要有两个:
- 通过 JSI,可以实现 JS 引擎的更换
- 通过 JSI,可以通过 JS 直接调用 Native
- JS 对象可以直接获得 C++ 对象(Host Objects)引用,从而允许 JS 与 Native 的直接调用
- 减少不必要的线程通信
- 省去了序列化和反序列化的成本
- 减轻了通信压力,提高了通信性能
优化 Bridge 层
- Fabric
- 简化了 UI 渲染
Fabric 简化了 React Native 渲染,简化之前渲染流程中,有复杂跨线程交互(React -> Native ->Shadow Tree -> Native UI)。优化之后,直接在 C++ 层创建 JavaScript 与 Native 共享的Shadow Tree,并通过 JSI 层将 UI 操作接口暴露给 JavaScript,允许 JavaScript 直接控制高优先级的 UI 操作,甚至允许同步调用(应对列表快速滚动、页面切换、手势处理等场景)。这样避免了跨线程的操作,极大地提高了UI的响应速度。
- Turbo Modules
- 通过 JSI,可以让 JS 直接调用 Native 模块,实现同步操作
- 实现 Native 模块按需加载,减少启动时间,提高性能
之前所有 Native Modules(无论是否需要用到)都要在应用启动时进行初始化,因为 Native 不知道 JS 将会调用哪些功能模块。而新的 Turbo Modules 允许按需加载 Native 模块,并在模块初始化之后直接持有其引用,不再依靠消息通信来调用模块功能。因此,应用的启动时间也会有所提升
精简核心(Lean Core)
- 将 react-native 核心包进行瘦身
- RN 推出多年,其核心包太过臃肿
- 有些包在项目中用不到,每次也要引入,造成资源浪费
- 非必要的包,移到社区模块,单独维护
- 例如:AsyncStorage、WebView 等
新架构启动流程