likes
comments
collection
share

React Native 新旧架构对比

作者站长头像
站长
· 阅读数 2

在 RN 社区的 Upgrade Helper 中可以看到,从 0.67 版本到 0.68 版本的更新中有非常显眼的字眼:

React Native 0.68 includes preview of the New Architecture opt-in.

React Native 新旧架构对比

那么今天我们就来看下 RN New Architecture 到底新在哪里。

新旧架构对比

开门见山,我们先来对比下新旧两个架构图。

旧架构图: React Native 新旧架构对比

新架构图: React Native 新旧架构对比

我们可以明显看到新架构相比旧架构有着以下几点明显不同:

  1. 最左侧 JS 层除了 React 之外,还引入了一个静态类型定义 Static Typs
  2. JS 打包成 JS Bundle 这步操作也不再强依赖 JS Core,任何 JS 引擎都可以实现
  3. JS 打包成 JS Bundle 后,不再是简单的通过 JSBridge 与 Native 层进行交互,而是要经过 JSI、Fabric、Turbo Modules 这三个新模块来实现与 Native 层的交互
  4. 在 JS 层和 JSI 之间,还有一个新东西叫 CodeGen
  5. JS 线程 与 用于布局的 Shadow 线程 之间的交互也不再需要经过 Native/UI 线程,直接通过 JSI 就可以完成交互

其中最关键的几个变化如下:

  • JSI(Javascript Interface)
  • Fabric
  • Turbo Modules
  • CodeGen

下面我们来依次看下这几个关键变化都带来了哪些重要的改变。

新旧通信方式对比

旧架构的通信方式

在旧架构中,JS 与 Native 之间的通信是通过 JSBridge 来完成的。 React Native 新旧架构对比

当 JS 调用某个 Native 方法(比如开启蓝牙权限)时,一般会执行以下事项:

  1. JS 线程将事件消息序列化成 JSON
  2. JS 线程将序列化后的 JSON 信息传递给 JSBridge
  3. JSBridge 将信息传递给 Native 之前,会先将其反序列化
  4. Native 线程接收到反序列化后的信息,并执行对应的 Native 代码

旧的通信方式存在以下问题:

  1. 所有消息队列都是异步处理的。虽然大部分情景下异步处理是合理的,但是总避免不了需要同步调用的时候
  2. 消息传递都需要经过序列化与反序列化,会带来额外的性能开销
  3. 对 Native 的调用是需要进行排队,批处理的

大部分时候Bridge的工作方式没问题,但有时候可能会发生阻塞现象。

比如当页面中有有个滚动列表,需要滚动时动态请求数据并加载,这时候如果用户快速滚动,可能就会出现白屏。

这是由于 Native 的滚动事件会经过 JSBridge 传递给 JS 线程,JS 线程再去发送请求获取下一步渲染数据,然后再将新的渲染数据通过 JSBridge 传递给 Native 线程。这些通信过程都是异步的,就有可能导致页面空白。

新架构的通信方式

前面提到的 JSI 支撑起了 RN 新架构的通信方式。它是一个轻量级的通用层,它是用 C++ 实现的,可以让 JS 引擎直接调用到 Native 端的方法

React Native 新旧架构对比

为什么说它是通用层呢?

旧架构使用的是 JSCore 引擎,而 JSBridge 只能兼容这个特定的引擎。然而对于 JSI 而言,它与引擎完全解耦,理论上可以在任何 JS 引擎(比如 V8、Hermes)上使用。

JSI 又是如何让 JS 可以直接调用到 Native 的呢?

Native 方法通过 C++ 宿主对象暴露给 JS。通过 JSI 接口,JS 对象可以直接引用 C++ 对象,并调用到它所暴露的接口。反之,C++ 对象也可以直接引用并调用 JS 对象的方法。

总而言之,JSI 实现了 JS 对象 与 Native 对象间的同步调用,同时也解决了消息传递需要序列化和反序列化的问题。

同时 JSI 还有另一大优点,它是用 C++ 编写的,凭借 C++ 的强大功能,在未来 React Native 也可以在其他系统上运行,比如智能电视、智能穿戴设备等等。

新旧渲染器对比

旧架构的渲染器

旧架构的渲染器是 UI Manager,当我们执行页面渲染时,它是这么运行的:

  1. React 在 JS 侧会根据代码创建一个 ReactElementTree
  2. 渲染器会根据 ReactElementTree 在 C++ 层创建一个 ReactShadowTree
  3. 布局引擎(比如 Yoga)会处理这个 ReactShadowTree 并计算出元素的布局位置
  4. 处理完成后,ShadowTree 会被转换成由 Native 组件构成的 HostViewTree(比如 View 组件会被转换成 Native 的 ViewGroup 组件)

React Native 新旧架构对比

由于旧架构中通信都是要经过 JSBridge 的,因此旧架构的渲染器也会存在转换慢以及重复数据等问题。同时由于通信非同步,会存在前面提到的渲染阻塞问题。

新架构的渲染器 —— Fabric

Fabric 是 RN 在新架构中提出的新一代渲染器。

Fabric 借助了 JSI 的能力,可以做到 JS 线程 和 UI 线程之间的同步通信。这对于用户体验是有极大的提升的。

比如用户在滚动列表 / 操作手势时,这些行为都可以同步的反馈给用户,而不再像以前那样可能会出现阻塞现象。

另外,新的 Shadow Tree 会在 JS 线程 和 UI 线程之间共享,运训来自两端都的直接交互。

新旧 NativeModules 对比

旧架构的 NativeModules

在旧架构中,所有 NativeModules 在 App 启动时都需要被初始化:

  1. 首先 Native 开发者先定义好接口,然后把 NativeModules 注册进一个 Module 列表中
  2. RN 在启动时,会把所有的 Modules 初始化,并生成一份映射表,这份映射表会被注入到 C++ 层和 JS 层
  3. 这样,在 Native 层、C++ 层和 JS 层都存在同一份映射表,JS 可以通过 JSBridge 调用映射表中对应的 Native 方法

旧架构存在一个很明显的问题:即使用户可能永远不会使用到某个 NativeModules,这个 Module 还是会在应用已启动时就被初始化。这有可能会影响到应用的启动时间

新架构的 NativeModules —— TurboModules

新架构的 NativeModules 叫做 TurboModules:

  1. JS 层首先定义好 Module 的接口(即上面提到的 Static Types),可以用 Typescript 定义
  2. RN 通过 CodeGen 会生成该接口对应的各个平台的 Native 接口,Native 侧只需要继承这个接口去做具体的实现即可
  3. 由于借助了 JSI 的能力,RN 可以做到只有当 JS 调用到某个 Module 时,才去执行它的初始化,做到真正的模块懒加载

CodeGen

JS 是一种动态类型语言,但是 JSI 是基于 C++ 的, C++ 是一种静态类型语言。

为了确保二者之间能够顺畅的通信,新架构中引入了静态类型检查器 —— CodeGen。

CodeGen 使用 JS 的类型定义(比如 Typescript)作为声明来源,来生成 Fabric 和 TurboModules 所使用的接口元素。同时 CodeGen 还可以在构建时生成对应的 Native 代码,减少运行时的开销。

总结

RN 新架构对比旧架构有几个关键的变更:

  • 使用 JSI 替代 JSBridge
  • 使用 Fabric 替代 UI Manager,更像 Web 端的渲染系统
  • 使用 TurboModules 替代 NativeModules,支持 Native 模块懒加载
  • 支持任何 JS 引擎上运行的潜力
  • 支持同步通信
  • 支持兼容 JS 和 Native 之间的静态类型检查

参考

不管您是从未接触过 RN 的小白,还是已经精通 RN 的大佬,都欢迎加入 React Native 技术交流群,一起畅所欲言吧~ React Native 新旧架构对比

转载自:https://juejin.cn/post/7212143399037190181
评论
请登录