深入探究React Native的新架构

3,925 阅读10分钟

本文(引用之Link),旨在深入探究React Native的新架构,重新构建带来最重要变化:

  • JavaScript接口(JSI)
  • Fabric
  • Turbo模块
  • CodeGen。

在此之前,回顾一下当前RN架构是如何工作的。当你运行一个RN应用程序时,所有JavaScript代码都打包到一个名为JS Bundle的软件包中,并与本地代码分开存储。执行React Native应用程序在三个线程上进行:

  • 1.JavaScript线程;
  • 2.本地/UI线程;
  • 3.阴影线程。其中引入了新架构后涉及到以上几种内容的改进和更新

JS和Native线程之间的通信是通过一个称为bridge的实体进行的。通过bridge发送数据时,需要将其批处理(优化)并序列化为JSON格式。此桥仅能处理异步通信。

image.png

一些重要术语:

  • JavaScriptCore:它是一个JavaScript引擎的名称,React Native使用它来执行JS代码。
  • Yoga:它是一个布局擎的名称,在计算UI元素在用户屏幕上的位置方面使用。

1. JavaScript接口(JSI)

在当前的架构中,React Native使用桥模块来实现JS和本地线程之间的通信。每次通过bridge发送数据时,都必须将其序列化为JSON格式,并在接收到数据后进行解码。

这意味着JavaScript和Native世界彼此不知道(即JS线程无法直接调用Native线程上的方法)

另一个重要点需要注意的是:通过bridge发送的消息是异步性质的,大多数情况下可以正常工作,但某些情况下需要将JS代码与本地代码同步。

举个例子更好地理解Bridge的作用:

如果JavaScript线程需要访问某些本地模块(例如蓝牙),它将需要向本地线程发送一个消息。

JS线程将通过Bridge发送一个序列化JSON消息,桥会优化此消息并将其发送到本地线程。

消息会在本地线程上进行解码,并最终执行所需的本地代码。

image.png

  • 1.JS线程为本地线程准备消息
  • 2.在跨过桥之前,它被序列化为JSON格式。
  • 3.当在Bridge的另一端接收到时解码
  • 4.然后本地线程执行所需的本地代码。

然而,在新架构中,桥将被一个名为 JavaScript接口 的模块替换,这是一个轻量级、通用的层,使用C++编写,可以让JavaScript引擎直接调用本地领域中的方法。

“General-purpose” 是什么意思?

当前架构使用 JavaScriptCore 引擎。这个 bridge 只兼容这个特定引擎。但是,JSI 不一样。JavaScript Interface 将会和 Engine 解耦,这意味着新的架构可以使用其他 JavaScript 引擎,如 Chakra、v8、Hermes 等等。因此术语 “general-purpose”。

Hermes

Hermes 是专门针对 React Native 应用而优化的全新开源 JavaScript 引擎。对于很多应用来说,启用 Hermes 引擎可以优化启动时间,减少内存占用以及空间占用。从 React Native 0.70 版本开始 Hermes 已经默认启用,无需开发者再做任何配置。

  • 使用 Hermes可以获得更好的性能和可靠性。使用Hermes引擎相对于默认引擎可以获得30%左右的性能提升
  • 具体而言,Hermes 是一种基于单线程的 JavaScript 引擎,在内存管理、垃圾回收等方面都有优化,因此与默认引擎比较时可以显著减少 CPU 和内存使用量。
  • 此外,由于 Hermes 执行速度更快,在处理大型应用程序或文件时也表现得更加流畅。
  • 最后值得一提的是,Hermes 的构建时间也比默认引擎要短很多,并且可以与 JSC 进行无缝兼容,以便开发者根据需求选择适合自己项目的解决方案。

JSI 如何使 JavaScript 直接调用本地方法?

通过 JSI,本地方法将通过 C++ Host 对象向 JavaScript 公开。JavaScript 可以持有对这些对象的引用,并可以使用该引用直接调用方法。这类似于 Web,在那里 JavaScript 代码可以持有对任何 DOM 元素的引用,并调用其方法。例如:当您编写:

const container = document.createElement(‘div’);

这里,容器是一个 JavaScript 变量,但它持有对一个 DOM 元素的引用,这个 DOM 元素可能在 C++ 中初始化。如果我们在“容器”变量上调用任何方法,它将反过来调用 DOM 元素上的方法。JSI 将以类似的方式工作。

与 Bridge 不同,JSI 将允许 JavaScript 代码持有对本地模块的引用。通过 JSI,JavaScript 可以直接在此引用上调用方法。

image.png

  • 1.JavaScript 直接引用一个本地模块
  • 2.它通过 JavaScript Interface 调用这个本地模块的方法

总之,JSI 将使其他 JavaScript 引擎可以使用,并且允许线程之间完全互操作。JavaScript 代码可以直接从 JS 线程与本地侧通信。这将消除序列化 JSON 消息的需要,并解决桥接器上的拥塞和异步问题。

JSI 的另一个重要优点是它使用 C++ 编写。借助 C++ 的强大功能,React Native 可以针对许多系统进行定位,比如智能电视、手表等等。

2. Fabric

Fabric是渲染系统,将取代当前的UI Manager。

为了了解Fabric的优势,让我们先看一下React Native中当前的UI渲染方式:

当你运行应用程序时,React会执行你的代码并在JavaScript中创建一个ReactElementTree。基于这个树结构,Renderer在C ++中创建一个ReactShadowTree。

布局引擎使用该影子树计算主机屏幕上UI元素的位置。一旦布局计算结果可用,影子树就被转换成HostViewTree,该树由本地元素组成。(例如:ReactNative元素分别会翻译成Android中的ViewGroup和iOS中的UIView)

image.png

ReactElementTree (JavaScript) -> ReactShadowTree(C++) -> HostViewTree(Native)

问题在于这种方法:

正如我们所知,所有线程之间的通信都是通过桥接器进行的。这意味着传输速率较慢,而且数据也会不必要地复制。

例如:如果ReactElementTree节点恰好是,那么ReactShadowTree的后续节点也将是一个图像。但是该数据必须被复制并分别存储在两个节点中。

这还不止。由于JS和UI线程不同步,在某些用例中,你的应用程序可能会因为卡顿导致帧数下降。(例如:滚动包含大量数据列表的FlatList)

什么是 Fabric?

“Fabric 是 React Native 的新渲染系统,是传统渲染系统的概念进化”。

正如我们在此文章的 JSI 部分中所看到的那样,JavaScript 接口将直接向 JavaScript 公开本机方法,其中也包括 UI 方法。由于这一点,JS 和 UI 线程可以同步。这将改善列表、导航、手势处理等性能表现。

Fabric的好处是什么?

通过新的渲染系统,可以将用户交互(如滚动、手势等)优先执行在主线程或本地线程中同步执行。而其他任务(比如API请求)则会异步执行。

这还不是全部。新的阴影树将是不变的,并且它将在JS和UI线程之间共享,以允许双向直接交互。

正如我们所看到,在当前架构中,React Native需要维护两个层次结构/DOM节点。但由于阴影树现在将在各领域共享,因此也有助于减少内存消耗。

原理

Fabric UI 管理器是一个基于 React Fiber 的高性能渲染引擎,它可以优化布局、绘制和动画等方面的性能。 具体实现原理包括:

  • 使用 Shadow Tree:使用可读写的 Shadow Tree 存储组件树,并采用 Diffing 策略对组件进行重新排列和重绘。
  • 异步计算布局:使用异步计算布局方式避免了同步阻塞主线程,从而提高响应速度。
  • 批量处理/缩减渲染操作:Fabric 会将多个需要更新的组件合并为一次操作执行,以减少无谓的 UI 更新导致 GPU 过载问题。
  • GPU 加速绘制:使用 OpenGL ES 2.0 等可编程管线对组件进行 GPU 渲染,从而提供更快的渲染效率。 以上技术相互协作,可以大幅度提高 React Native 应用程序的 UI 渲染性能。

3. TurboModules

在当前的架构中,JavaScript 使用的所有本地模块(例如蓝牙、地理位置、文件存储等)都必须在应用程序打开之前初始化。这意味着,即使用户不需要特定的模块,它仍然必须在启动时进行初始化。

TurboModules 基本上是对旧的原生模块的一种增强。正如我们在本文前面看到的那样,现在JavaScript将能够持有对这些模块的引用,这将允许JS代码仅在需要时加载每个模块。这将显着改善ReactNative应用程序的启动时间。

原理

TurboModules 通过将JavaScript代码直接转换为C++代码并建立JS线程和原生线程之间的通信,避免了跨语言通信的性能瓶颈,从而提高整个React Native应用程序的性能。

4. CodeGen

所有对 TurboModules 和 Fabric 的讨论听起来都很有前途,但是 JavaScript 是一种动态类型语言,而 JSI 是用 C++ 编写的静态类型语言。因此,需要确保两者之间的通信顺畅。

这就是为什么新架构还将包括一个名为CodeGen的静态类型检查器。

通过将类型化的JavaScript作为真相源,CodeGen 将定义 TurboModules 和 Fabric 使用的界面元素。 它还会在构建时生成更多本地代码,而不是运行时。

CodeGen 自动生成原生代码

  • Codegen 是一个命令行工具,可以自动生成一些原生模块需要使用到的文件或类等内容,并且这些生成好的内容已经遵守了所需规范标准。
  • 0.71 版本中引入了 CodeGen 技术,旨在提高开发效率和减少出错率。
  • CodeGen 可以自动生成原生代码,并将其与 JavaScript 代码无缝衔接,让开发者更加专注于业务逻辑的编写。
  • 同时由于自动生成的内容已经遵守规范标准,也可以提高应用程序的稳定性和可维护性。

CodeGen 的实现原理主要包括以下几个步骤:

  • 读取配置:从指定的配置文件中读取需要生成的模块名称、导出方法等信息。
  • 解析代码:解析 JavaScript 模块里面对应的流程控制语句,并将其转化为目标语言(如 Java 或 Objective-C)可识别的形式。
  • 构建对象模型:按照所需规范自动创建类和接口等内容,在必要时使用反射机制完成各种数据结构和声明映射以及桩部分处理。
  • 自动生成文件:根据对象模型

总结:

如果将所有变更组合起来,新架构将如下所示:

image.png 以下是主要亮点:

  • Bridge 将被 JSI 替换
  • 可以用其他引擎替换 JavaScriptCore
  • 所有线程之间完全的互操作性
  • 类似 Web 的渲染系统
  • 可以同步执行时间敏感任务
  • TurboModules 可以进行延迟加载
  • 静态类型检查,用于 JS 和 Native 端兼容性。

我们可以确定这种新结构将为 React Native 带来一些强大的改进。

Hermes RN 性能提升

hermes-default-android-data.png

android

hermes-default-ios-data.png

ios

hermes-default-android-video.gif

android 首屏

hermes-default-ios-slow-video.gif

ios 首屏