各位开发者、技术爱好者们,大家下午好!
今天,我们将深入探讨 React Native 渲染器领域的一个里程碑式变革——Fabric。在移动应用开发日益追求极致性能和原生体验的今天,Fabric 渲染器被誉为 React Native 迈向未来的基石。它不仅仅是一个简单的更新,而是对 React Native 核心架构的彻底重构,旨在解决长期困扰框架的性能瓶颈和异步交互限制。
我们将从 Fabric 的诞生背景开始,回顾 React Native 旧有架构的局限性,然后逐步剖析 Fabric 如何通过引入新的 JSI (JavaScript Interface)、新的渲染管线以及 TurboModules 等创新技术,最终赋予 React Native 应用与原生应用同步、流畅交互的能力。
一、 回顾与痛点:旧版 React Native 架构的挑战
在深入了解 Fabric 之前,我们有必要回顾一下 React Native 在 Fabric 出现之前的架构。这个架构,我们通常称之为“Bridge”架构,是 React Native 早期成功的基础,但也正是它,埋下了许多性能和交互上的隐患。
1.1 Bridge 架构的核心思想
React Native 的核心理念是允许开发者使用 JavaScript 和 React 编写移动应用,并将其编译成真正的原生组件。为了实现这一目标,需要一个机制来连接 JavaScript 运行环境和原生平台。这个机制,就是“Bridge”(桥)。
在 Bridge 架构中,主要存在三个关键线程:
- JavaScript 线程 (JS Thread):这是运行所有 JavaScript 代码的地方,包括 React 逻辑、组件树的构建、业务逻辑、状态管理等等。JavaScript 引擎(如 Hermes 或 JSC)在此线程上运行。
- 原生 UI 线程 (Native UI Thread):也称为主线程。这是操作系统负责渲染用户界面、处理用户输入事件的线程。所有实际的原生视图(例如 iOS 上的
UIView、Android 上的android.view.View)都是在这个线程上创建、布局和绘制的。 - 阴影线程 (Shadow Thread):这个线程(通常由 C++ 实现)负责计算 React 组件在屏幕上的布局。它接收 JavaScript 线程发送的布局指令,并使用 Yoga 布局引擎计算出每个组件的最终尺寸和位置。计算完成后,它会将这些布局信息发送到原生 UI 线程。
1.2 Bridge 的工作原理
Bridge 的核心作用是实现 JavaScript 线程与原生线程之间的通信。这种通信本质上是异步的,并且通过序列化和反序列化消息进行。
-
JavaScript 调用原生:
- 当 JavaScript 代码需要调用一个原生模块(例如,访问设备的摄像头、存储数据、显示原生 Toast 消息)时,它会构造一个包含模块名称、方法名和参数的 JSON 字符串。
- 这个 JSON 字符串通过 Bridge 发送到原生端。
- 原生端接收到 JSON 字符串后,对其进行反序列化,解析出模块和方法,然后执行相应的原生代码。
- 如果原生方法有返回值,返回值也会被序列化成 JSON 字符串,再通过 Bridge 返回给 JavaScript 线程,通常通过回调函数或 Promise 来接收。
-
原生事件通知 JavaScript:
- 当原生端发生某些事件(例如,用户点击了一个按钮、网络请求完成、传感器数据更新)需要通知 JavaScript 时,原生代码会构造一个包含事件类型和数据的 JSON 字符串。
- 这个 JSON 字符串通过 Bridge 发送到 JavaScript 线程。
- JavaScript 线程接收到 JSON 字符串后,对其进行反序列化,然后触发相应的 JavaScript 事件处理器。
1.3 Bridge 架构的固有局限性
尽管 Bridge 架构在早期取得了成功,但其异步和序列化的特性,随着应用复杂度的增加和用户对体验要求的提高,逐渐暴露出了一系列严重的局限性:
-
异步通信导致的 UI 卡顿 (Jank) 和不流畅
- 核心问题: JavaScript 线程和原生 UI 线程之间的通信是异步的。这意味着当 JavaScript 线程需要更新 UI 时,它发送指令给原生端,但原生端需要时间来接收、解析并执行这些指令。在这个“等待”过程中,如果用户进行了其他操作(如滑动),UI 可能无法立即响应,从而出现卡顿、掉帧,导致用户体验不佳。
- 举例: 一个复杂的动画或手势操作,需要 JavaScript 线程频繁地与原生 UI 线程交互。由于异步延迟,动画可能无法跟上用户的指尖移动,出现明显的滞后感。
-
序列化/反序列化开销
- 核心问题: 每次 JavaScript 和原生之间传递数据,都需要进行 JSON 字符串的序列化和反序列化。对于大量数据或高频通信的场景,这个过程会消耗大量的 CPU 时间和内存,成为显著的性能瓶颈。
- 举例: 一个包含数百个列表项的数据,如果每次更新都需要通过 Bridge 传递,其序列化开销将非常巨大。
-
无共享内存,数据复制
- 核心问题: JavaScript 运行时和原生运行时是两个独立的内存空间。这意味着数据在两者之间传递时,必须进行复制,而不是共享引用。这不仅增加了内存消耗,也进一步加剧了性能问题。
- 举例: JavaScript 中有一个大型对象,需要在原生模块中使用。Bridge 会将其序列化为字符串,然后在原生端反序列化为原生对象,这涉及两次完整的内存分配和数据复制。
-
难以实现真正的同步交互
- 核心问题: Bridge 的异步特性使得 JavaScript 无法直接、同步地获取原生 UI 状态或执行某些需要即时反馈的原生操作。虽然可以通过回调或 Promise 模拟同步,但本质上仍是异步的,无法满足一些对实时性要求极高的场景。
- 举例: 假设我们想在 JavaScript 中立即测量一个原生视图的尺寸。在 Bridge 架构下,我们需要调用
UIManager.measure方法,并传入一个回调函数。测量结果会在稍后的某个时间点通过回调返回。这对于需要在测量结果出来后立即执行后续逻辑的场景,带来了很大的编码复杂性和性能挑战。
-
JavaScript 线程阻塞
- 核心问题: JavaScript 线程不仅负责 UI 逻辑,还负责所有业务逻辑。如果 JavaScript 线程执行了耗时操作(例如复杂的计算、大量数据处理),它将无法及时处理 UI 更新和事件响应,导致整个应用卡死。
- 举例: 在 JavaScript 线程中执行了一个 CPU 密集型任务,用户尝试滑动列表,但列表完全没有反应,直到任务完成。
这些痛点,在构建复杂的、高性能的 React Native 应用时变得尤为突出。为了突破这些限制,Facebook(现在的 Meta)开始着手设计全新的 React Native 架构,这就是 Fabric。
二、 破茧成蝶:Fabric 渲染器登场
Fabric 是 React Native 团队对核心渲染器进行的彻底重写。它的目标是构建一个更现代化、高性能、可扩展的架构,以实现真正与原生应用媲美的用户体验。Fabric 旨在解决 Bridge 架构的所有核心痛点,尤其是实现 JavaScript 和原生之间的同步交互能力。
Fabric 架构的核心思想可以概括为以下几个关键支柱:
- 新的 JavaScript Interface (JSI):取代了传统的 Bridge,实现了 JavaScript 和 C++ 之间的直接、同步通信。
- 新的渲染器 (Fabric Renderer):引入了一个 C++ 层的“Shadow Tree”,用于管理 React 组件的布局和属性,并能够以更高效、更同步的方式更新原生 UI。
- 新的原生模块系统 (TurboModules):利用 JSI 提供的能力,构建了更高效、类型安全、可懒加载的原生模块。
- 新的事件系统:优化了原生事件向 JavaScript 的派发机制。
接下来,我们将逐一深入探讨这些核心组件,理解 Fabric 如何通过它们实现革命性的飞跃。
三、 核心基石:JavaScript Interface (JSI)
JSI (JavaScript Interface) 是 Fabric 架构的基石,也是实现同步交互的关键。它彻底取代了旧有的 Bridge 机制,为 JavaScript 运行时和 C++ 宿主环境提供了一种直接、高效、同步的通信方式。
3.1 JSI 的本质
JSI 的本质是一个轻量级的 C++ 层,它允许 JavaScript 引擎(如 Hermes 或 JSC)直接持有 C++ 对象的引用,并直接调用 C++ 方法。反之亦然,C++ 代码也可以直接持有 JavaScript 函数的引用并调用它们。
这意味着:
- 告别 JSON 序列化: 不再需要将数据序列化成 JSON 字符串,再通过异步消息队列传递。数据可以直接在 JavaScript 和 C++ 之间共享或传递引用。
- 同步调用: JavaScript 可以直接调用 C++ 函数并立即获得返回值,就像调用普通的 JavaScript 函数一样。这解决了 Bridge 架构中最大的痛点之一——异步通信导致的延迟。
- 共享内存: 通过 JSI,JavaScript 和 C++ 可以更方便地访问和操作同一块内存区域(或传递内存地址),从而避免了大量的数据复制。
3.2 JSI 的工作原理与概念
JSI 引入了一些核心概念:
jsi::Runtime:代表 JavaScript 运行时的实例。jsi::Value:代表 JavaScript 值(如数字、字符串、对象、函数等)在 C++ 端的抽象。jsi::Object:代表 JavaScript 对象在 C++ 端的抽象。jsi::Function:代表 JavaScript 函数在 C++ 端的抽象。jsi::HostObject:一个 C++ 对象,它可以被注册到 JavaScript 运行时,使得 JavaScript 代码能够像访问普通 JavaScript 对象一样访问它的属性和方法。jsi::HostFunction:一个 C++ 函数,它可以被注册到 JavaScript 运行时,使得 JavaScript 代码能够像调用普通 JavaScript 函数一样调用它。
当 JavaScript 需要调用一个原生方法时:
- C++ 端会创建一个
jsi::HostFunction或jsi::HostObject,并将其注册到jsi::Runtime中,使其在 JavaScript 全局对象或某个命名空间中可见。 - JavaScript 端直接调用这个通过 JSI 暴露出来的函数或访问其属性。
- JSI 负责将 JavaScript 调用转换为 C++ 调用,参数直接传递(而非序列化),返回值也直接返回。整个过程是同步的。
3.3 JSI 带来的优势
- 真正的同步交互: 这是最显著的优势。JavaScript 可以实时获取原生状态、执行原生操作,极大地提升了用户体验。
- 性能大幅提升: 消除了序列化/反序列化的开销,减少了数据复制,使得 JavaScript 和原生之间的通信速度提高了几个数量级。
- 内存效率更高: 减少了不必要的数据复制。
- 更紧密的集成: 允许 JavaScript 更直接、更细粒度地控制原生功能,为更高级的集成模式打开了大门。
- 类型安全(结合 CodeGen): 虽然 JSI 本身是 C++ 接口,但结合代码生成工具(如
rn-tester中的 CodeGen),可以为 JSI 接口生成类型定义,从而在编译时捕获类型错误,提升开发效率和代码质量。
3.4 JSI 概念性代码示例
假设我们有一个 C++ 函数,用于获取当前设备的时间戳:
// C++ 实现
#include <jsi/jsi.h>
#include <chrono>
namespace myApp {
namespace jsi = facebook::jsi;
// C++ 函数,获取当前时间戳
double getCurrentTimestampNative() {
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
// 将 C++ 函数暴露给 JavaScript
void install(jsi::Runtime& runtime) {
auto hostFunction = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "getCurrentTimestamp"),
0, // 期望的参数数量
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
// 在这里调用实际的 C++ 函数
double timestamp = getCurrentTimestampNative();
return jsi::Value(runtime, timestamp);
}
);
// 将 hostFunction 注册到全局对象 'global' 下的 'MyAppNative' 对象中
runtime.global().setProperty(
runtime,
"MyAppNative",
jsi::Object::createFromHostObject(runtime, std::make_shared<jsi::HostObject>(
// 这是一个简化的HostObject,实际上会更复杂
// 这里只是为了演示如何将函数挂载到对象上
))
);
runtime.global().getProperty(runtime, "MyAppNative").asObject(runtime).setProperty(
runtime,
"getCurrentTimestamp",
std::move(hostFunction)
);
}
} // namespace myApp
在 JavaScript 端,我们可以这样同步地调用它:
// JavaScript 代码
// 假设 install 函数已经将 MyAppNative 暴露到全局
if (global.MyAppNative && global.MyAppNative.getCurrentTimestamp) {
const timestamp = global.MyAppNative.getCurrentTimestamp(); // 同步调用!
console.log('Current timestamp from native (synchronous):', timestamp);
// 进一步的 JS 逻辑,立即使用 timestamp
if (timestamp > someThreshold) {
// ...
}
} else {
console.warn('MyAppNative.getCurrentTimestamp not available.');
}
这个简单的例子展示了 JSI 如何允许 JavaScript 同步调用 C++ 函数并获取返回值,这在 Bridge 架构中是无法直接实现的。这是 Fabric 能够实现与原生同步交互的基石。
四、 核心变革:Fabric 渲染器本身
JSI 提供了通信管道,而 Fabric 渲染器则是利用这个管道,彻底改变了 React Native 的 UI 渲染流程。它将 React 的声明式 UI 描述转换为实际的原生视图。
4.1 Fabric 渲染器的核心组件
Fabric 渲染器引入了几个关键的 C++ 层组件,它们协同工作以实现高效的 UI 渲染:
-
Shadow Tree (C++):
- 作用: 这是
Fabric最核心的概念之一。它是一个 C++ 层的对象树,镜像了 React 组件树的结构,但存储的是用于原生 UI 布局和渲染所需的所有信息(如样式、属性、布局计算结果等)。 - 布局计算:
Shadow Tree利用Yoga布局引擎(一个由 Facebook 开发的跨平台布局引擎,实现了 Flexbox 布局规范)在非主线程(通常是 C++ 层的布局线程)上进行布局计算。 - 同步性:
Shadow Tree的存在是实现同步交互的关键。JavaScript 可以通过 JSI 同步地访问和查询Shadow Tree中任何节点的布局信息(例如,它的尺寸、位置)。
- 作用: 这是
-
UIManager (C++):
- 作用:
UIManager也是一个 C++ 组件,它负责管理Shadow Tree的生命周期,并最终将Shadow Tree中的布局和属性信息“提交”到实际的原生 UI 线程,以创建、更新或销毁原生视图。 - 协调: 它扮演着协调者的角色,在
Shadow Tree更新后,识别出需要对原生 UI 进行的最小化变更集。
- 作用:
-
Host Views (Native Platform):
- 作用: 这些是最终呈现在用户屏幕上的实际原生 UI 组件,例如 iOS 上的
UIView、UILabel、UIButton,或 Android 上的android.view.View、TextView、Button等。 Fabric渲染器并不直接创建这些原生视图,而是通过UIManager发送指令给原生 UI 线程,由原生层来创建和管理它们。
- 作用: 这些是最终呈现在用户屏幕上的实际原生 UI 组件,例如 iOS 上的
4.2 Fabric 渲染管线详解
现在,让我们详细看看 Fabric 渲染器如何将 React 组件转换为屏幕上的原生视图,以及同步交互是如何在其中发挥作用的:
| 阶段 | 线程/环境 | 描述 |
| JavaScript 代码和 React Reconciliation | JS 主线程 | React 负责协调 DOM 树的变化。在这里,Fabric 的不同之处在于它通过 JSI 与 C++ 层直接通信,而不是通过 Bridge。 Fabric. It has a big impact on how React Native apps work, making them faster and smoother. I’ll explain what it is, how it’s different from the old way of doing things, and why it’s so important for making React Native feel more like native apps. I’ll also show you some code examples to make it clearer.
引言:React Native 性能的演进与 Fabric 的使命
亲爱的技术同仁们,大家好!
在移动应用开发的浩瀚星空中,React Native 以其“一次学习,随处编写”的理念,迅速 завоевал 众多开发者的心。它允许我们用熟悉的 JavaScript 和 React 模式构建跨平台应用,极大地提高了开发效率。然而,早期 React Native 架构的“桥接”(Bridge)机制,也像一座横亘在 JavaScript 世界与原生世界之间的桥梁,在带来便利的同时,也带来了性能上的鸿沟。
这座桥梁,虽然连接了两岸,但其本质是异步的、基于消息序列化的。这意味着,当 JavaScript 需要与原生进行交互时,数据必须被打包、发送、解包,这个过程充满了延迟和开销。对于静态内容展示,这或许尚可接受,但对于需要高频、实时、同步反馈的场景,比如流畅的手势交互、复杂的动画、或是即时获取原生 UI 状态,旧架构的力不从心便暴露无遗。
想象一下,你正在用 React Native 开发一个绘图应用。用户用手指在屏幕上拖动,你希望线条能够即时、平滑地跟随指尖移动。在旧架构下,每次指尖位置变化,JavaScript 都需要通过 Bridge 异步地通知原生绘制新的线条。由于异步延迟和序列化开销,你可能会看到线条滞后于指尖,甚至出现断断续续的“卡顿”感。这种“非原生”的体验,正是旧架构的阿喀琉斯之踵。
为了弥补这一鸿沟,Facebook(现 Meta)的 React Native 团队 embarked on a monumental task:彻底重写其渲染层。这就是我们今天的主角——Fabric 渲染器。
Fabric 的核心使命是赋予 React Native 应用与原生应用同步交互的能力。它并非简单的修修补补,而是对整个渲染管线进行了深层重构,旨在消除 Bridge 带来的所有性能瓶颈,让 React Native 应用在响应速度、动画流畅度以及与原生功能的集成度上,达到与纯原生应用几乎无异的水平。
在接下来的内容中,我们将作为编程专家,以讲座的形式,一步步揭开 Fabric 的神秘面纱。我们将从旧架构的痛点出发,深入剖析 Fabric 如何通过 JSI、Shadow Tree 和 TurboModules 等核心技术,构建起一个更高效、更强大的渲染引擎,最终实现其“与原生同步交互”的宏伟愿景。我们将穿插代码示例和详细解释,力求逻辑严谨,通俗易懂。
二、 旧桥的困境:React Native Bridge 架构的深层剖析
在 Fabric 出现之前,React Native 的基石是其“Bridge”架构。理解这一架构的工作原理及其局限性,是我们理解 Fabric 变革价值的关键。
2.1 Bridge 架构的通信模型
React Native 应用在运行时,通常涉及到三个主要线程:
-
JavaScript 线程 (JS Thread):
- 职责:负责执行所有 JavaScript 代码。这包括 React 组件的生命周期管理、状态更新、数据处理、业务逻辑、网络请求等。
- 引擎:通常运行在 JavaScriptCore (JSC) 或 Hermes 等 JavaScript 引擎上。
-
原生 UI 线程 (Native UI Thread / Main Thread):
- 职责:这是操作系统的主线程,负责实际的 UI 渲染和用户事件处理。所有原生视图(如 iOS 的
UIView、UILabel;Android 的android.view.View、TextView)的创建、布局、绘制和事件响应都发生在这个线程上。 - 重要性:这个线程的阻塞会导致 UI 卡顿、应用无响应,是用户体验最直接的体现。
- 职责:这是操作系统的主线程,负责实际的 UI 渲染和用户事件处理。所有原生视图(如 iOS 的
-
布局线程 (Layout Thread / Shadow Thread):
- 职责:在旧架构中,这个线程主要负责处理 React Native 组件的布局计算。它接收 JavaScript 线程发送的样式属性,并使用
Yoga布局引擎(一个 C++ 实现的 Flexbox 布局库)计算出每个组件的最终尺寸和位置。 - 与 JS 线程通信:布局线程与 JavaScript 线程通过 Bridge 异步通信,获取更新的样式信息。
- 职责:在旧架构中,这个线程主要负责处理 React Native 组件的布局计算。它接收 JavaScript 线程发送的样式属性,并使用
Bridge 架构的核心在于,它通过一个异步的、批处理的、基于 JSON 消息的机制,在 JavaScript 线程和原生线程之间进行通信。
通信流程概览:
-
JS -> Native (调用原生模块/更新 UI):
- JavaScript 线程调用一个原生方法(例如
NativeModules.ToastAndroid.show(...)或UIManager.createView(...))。 - React Native 框架将这个调用(包括模块名、方法名、参数)序列化成一个 JSON 字符串。
- 这个 JSON 字符串被放入一个消息队列,并通过 Bridge 异步地发送到原生端。
- 原生端接收到 JSON 字符串后,对其进行反序列化,然后调用对应的原生模块方法或
UIManager的方法来操作原生视图。 - 如果原生方法有返回值,返回值也会被序列化成 JSON 字符串,通过 Bridge 返回给 JavaScript 线程(通常通过回调函数或 Promise)。
- JavaScript 线程调用一个原生方法(例如
-
Native -> JS (原生事件):
- 当原生端发生用户事件(如点击、滑动、键盘输入)或系统事件时。
- 原生代码将事件信息序列化成一个 JSON 字符串。
- 这个 JSON 字符串通过 Bridge 异步地发送到 JavaScript 线程。
- JavaScript 线程接收到 JSON 字符串后,对其进行反序列化,然后分发给相应的 React 组件事件处理器。
2.2 Bridge 架构的深层痛点
上述通信模型在初期虽然可行,但其固有特性随着应用复杂度的提升和性能要求的提高,逐渐暴露出以下深层痛点:
-
异步通信的固有延迟与 UI 卡顿 (Jank)
- 问题核心:JavaScript 线程和原生 UI 线程是相互独立的,通信需要跨越 Bridge。这个过程是异步的,存在固有的延迟。
- 对 UI 的影响:当 JavaScript 线程需要更新 UI 时,它发送指令给原生,但原生端不会立即收到并执行。用户在这一微小的时间窗口内进行的任何交互(例如滑动),都可能因为原生 UI 线程尚未收到新的指令而无法及时响应,导致视觉上的卡顿或动画不连贯(即“Jank”)。
- 示例:考虑一个高性能的拖拽手势。用户拖动一个元素,React Native 需要在 JavaScript 线程中计算新的位置,然后通过 Bridge 异步通知原生 UI 线程更新元素位置。如果这个过程耗时超过一帧(约 16ms),用户就会感觉到延迟,拖拽的元素会“滞后”于手指。
-
高昂的序列化/反序列化成本
- 问题核心:所有跨 Bridge 的数据传输都必须经过 JSON 字符串的序列化和反序列化。
- 性能影响:
- CPU 密集:将复杂的数据结构(如大型数组、嵌套对象)转换为 JSON 字符串,以及将其从 JSON 字符串解析回原生数据结构,都是 CPU 密集型操作。
- 内存开销:序列化后的 JSON 字符串本身需要内存,原生端和 JavaScript 端都需要额外内存来存储原始数据和序列化后的数据,造成内存冗余。
- 示例:一个包含大量数据点的图表组件,如果需要频繁地更新数据并通过 Bridge 传递,序列化/反序列化将成为主要的性能瓶颈,导致图表更新缓慢。
-
缺乏共享内存与数据复制
- 问题核心:JavaScript 运行时和原生运行时存在于独立的内存空间中。它们无法直接共享内存地址或引用。
- 后果:所有在两者之间传递的数据都必须进行复制。JavaScript 创建一个对象,通过 Bridge 传递给原生,原生会创建一份新的内存拷贝。
- 性能影响:数据复制不仅增加了内存消耗,也增加了 CPU 负担,尤其对于大型数据结构。
-
难以实现真正的同步原生交互
- 问题核心:Bridge 的异步特性是其核心设计。这意味着 JavaScript 无法直接、同步地获取原生 UI 元素的实时状态(例如,一个视图当前的精确尺寸或位置),也无法同步执行一些需要即时反馈的原生操作。
- 对开发的影响:开发者不得不依赖回调函数或 Promise 来处理原生操作的结果,这增加了代码的复杂性,也限制了某些高级交互模式的实现。
-
示例:在 JavaScript 中,如果需要测量一个原生视图的宽度,我们通常会这样做:
import { UIManager, findNodeHandle } from 'react-native'; const measureView = (ref) => { const node = findNodeHandle(ref); if (node) { UIManager.measure(node, (x, y, width, height, pageX, pageY) => { console.log('View dimensions (asynchronously):', { width, height }); // 只能在回调中处理结果 }); } };UIManager.measure是一个异步操作。在回调函数执行之前,JavaScript 无法知道视图的尺寸,这对于需要立即根据尺寸做出反应的逻辑(如在测量后立即调整另一个视图的位置)来说,非常不便且效率低下。
-
JavaScript 线程的单点瓶颈
- 问题核心:JavaScript 线程不仅处理 UI 更新指令,还负责所有的业务逻辑。
- 后果:如果 JavaScript 线程执行了耗时很长的计算任务,它就无法及时响应用户交互或处理 UI 更新指令,导致整个应用“假死”。UI 线程即使空闲,也因为没有新的指令而无法更新。
- 示例:一个复杂的数据处理算法在 JavaScript 线程中运行。在此期间,用户尝试滚动列表,但列表完全无响应,直到算法执行完毕。
这些深层痛点,是 Fabric 渲染器诞生的根本原因。它们不仅影响了 React Native 应用的性能表现,也限制了开发者构建真正“原生级”体验的能力。
三、 破局之道:Fabric 渲染器的新架构
Fabric 渲染器是 React Native 团队为了解决 Bridge 架构的上述痛点而推出的革命性解决方案。它从根本上重构了 React Native 的渲染管线,旨在实现更高效、更同步、更贴近原生的交互体验。
Fabric 的核心理念是:将更多的渲染逻辑和组件生命周期管理推向 C++ 层,并利用 JSI 实现 JavaScript 与 C++ 之间的高效同步通信。
3.1 Fabric 架构的四大支柱
Fabric 架构的实现依赖于以下几个关键的、相互关联的组件:
-
JavaScript Interface (JSI):
- 作用:取代了旧的 Bridge,作为 JavaScript 引擎(如 Hermes)与 C++ 宿主环境之间直接、同步的通信层。
- 核心特点:允许 JavaScript 直接持有 C++ 对象的引用并调用其方法,反之亦然,且无需序列化/反序列化。
- 地位:是
Fabric实现同步交互的基石。
-
Fabric渲染器本身:- 作用:一个 C++ 实现的渲染器,负责将 React 组件树转换为一个 C++ 层的“阴影树”(Shadow Tree),并在 C++ 层进行布局计算。
- 核心特点:引入了 C++
Shadow Tree,实现了布局计算的跨线程(非主 UI 线程)执行,并能以更高效的方式将更新提交到原生 UI 线程。
-
TurboModules:- 作用:新的原生模块系统,利用 JSI 的能力,实现原生模块的类型安全、懒加载和更高效的调用。
- 核心特点:原生模块直接通过 JSI 暴露给 JavaScript,消除了 Bridge 的序列化开销,并支持同步调用。
-
新的事件系统:
- 作用:优化了原生事件(如触摸、手势)向 JavaScript 的派发机制。
- 核心特点:实现事件的批处理和更直接的派发,减少延迟。
通过这四个支柱的协同作用,Fabric 旨在构建一个更健壮、性能更优、更具扩展性的 React Native 平台。
3.2 Fabric 架构下的线程模型
在 Fabric 架构下,线程模型也发生了显著变化,旨在最大限度地减少主 UI 线程的负担,并实现更流畅的交互:
-
JavaScript 线程 (JS Thread):
- 职责:仍然负责 React 的协调(reconciliation)过程、组件状态管理和大部分业务逻辑。
- 与 C++ 层通信:通过 JSI 与 C++ 层的
Fabric渲染器和TurboModules进行同步通信。
-
C++ 渲染线程 / 布局线程 (C++ Layout Thread):
- 职责:这是
Fabric引入的核心改变之一。它是一个 C++ 线程,专门负责构建和更新Shadow Tree,并使用Yoga引擎进行所有组件的布局计算。 - 重要性:布局计算是 CPU 密集型任务,将其从 JS 线程或主 UI 线程中剥离出来,可以在后台高效执行,避免阻塞主线程。
- 职责:这是
-
原生 UI 线程 (Native UI Thread / Main Thread):
- 职责:依然负责最终的原生视图的创建、更新、销毁和事件处理。
- 与 C++ 层通信:通过 C++ 层的
UIManager接收来自Shadow Tree的最终布局和属性更新,然后以最高效的方式操作原生视图。这个提交过程在 C++ 层看来是同步的,对于原生 UI 线程来说,它接收到的就是已经计算好的、可以直接应用的变更。
关键差异总结:
在 Bridge 架构中,JS 线程、布局线程和原生 UI 线程之间的通信都是异步的,并且经过 JSON 序列化。
而在 Fabric 架构中:
- JS 线程与 C++ 层(渲染器、TurboModules)之间的通信是同步的(通过 JSI)。
- C++ 布局线程负责在后台进行布局计算。
- C++ 渲染器将计算好的更新以高效的方式“提交”给原生 UI 线程,这个提交过程在 C++ 层面是同步的,并确保原生 UI 线程能即时响应。
这种新的线程模型和通信机制是 Fabric 能够带来巨大性能提升和实现同步交互的核心秘密。
四、 JSI 的力量:同步通信的实现细节
我们已经提到 JSI 是 Fabric 的基石。现在,让我们更深入地探讨 JSI 是如何实现 JavaScript 和 C++ 之间直接、同步通信的。
4.1 JSI 的核心思想:引用与直接调用
传统的 Bridge 依赖于消息传递和序列化,就像通过邮局寄信一样,有固定的格式、地址和传输时间。而 JSI 更像是 JavaScript 和 C++ 之间建立了一条直通电话线,允许它们直接交谈、互相引用对方的“对象”,并同步地调用对方的“方法”。
JSI 的实现原理在于:
- C++ 作为宿主环境:React Native 的原生部分是 C++ 宿主环境。JavaScript 引擎(如 Hermes 或 JSC)被嵌入到这个 C++ 环境中。
- 相互引用:C++ 代码可以通过
jsi::Runtime访问 JavaScript 全局对象,并可以在其中注册 C++ 对象(jsi::HostObject)和 C++ 函数(jsi::HostFunction)。一旦注册,JavaScript 代码就可以像访问普通 JavaScript 对象和函数一样,直接访问这些 C++ 对象和函数。 - 直接内存访问:当 JavaScript 调用一个通过 JSI 暴露的 C++ 函数时,参数不再是序列化的 JSON 字符串,而是
jsi::Value类型。这些jsi::Value可以直接引用或包装底层的 C++ 数据结构,甚至可以传递原始内存地址(例如,对于大型数组或图像数据),从而避免了数据复制。
4.2 JSI 核心 API 概览
JSI 提供了一套 C++ API,用于在 JavaScript 运行时中操作 JavaScript 值和对象,以及将 C++ 功能暴露给 JavaScript:
jsi::Runtime:代表一个 JavaScript 虚拟机实例。所有 JSI 操作都围绕着一个jsi::Runtime对象进行。jsi::Value:JavaScript 值的 C++ 包装器。它可以是undefined、null、bool、number、string、symbol、object、function。jsi::String、jsi::Object、jsi::Function:分别是对 JavaScript 字符串、对象、函数的 C++ 包装器。jsi::PropNameID:表示属性名。jsi::HostObject:一个 C++ 类,可以继承jsi::HostObject并实现其虚方法,以便在 JavaScript 中像一个普通对象一样被访问。它的属性访问(get)和方法调用(set)都会转发到 C++ 实现。jsi::HostFunction:一个 C++ 函数,可以被注册到 JavaScript 运行时,然后 JavaScript 可以直接调用它。
注册 jsi::HostFunction 到 JavaScript:
#include <jsi/jsi.h>
#include <iostream>
namespace facebook {
namespace jsi {
// 假设我们有一个简单的 C++ 函数
int addNumbersNative(int a, int b) {
std::cout << "Calling addNumbersNative with " << a << ", " << b << std::endl;
return a + b;
}
// 这是一个 JSI install 函数,通常在 React Native 的原生初始化时调用
void installMyJSIModule(Runtime& runtime) {
// 1. 创建一个 jsi::HostFunction
// 参数1: runtime 引用
// 参数2: 在 JS 中可见的函数名
// 参数3: 期望的参数数量 (这里是 2,即 a 和 b)
// 参数4: 实际的 C++ 实现 lambda 函数
auto addNumbersFunc = Function::createFromHostFunction(
runtime,
PropNameID::forAscii(runtime, "addNumbers"), // JS 中调用的函数名
2,
[](Runtime& runtime, const Value& thisValue, const Value* arguments, size_t count) -> Value {
// 从 JS 传入的参数中提取值
if (count < 2 || !arguments[0].isNumber() || !arguments[1].isNumber()) {
throw JSIException("Expected two numbers as arguments.");
}
int a = arguments[0].asNumber();
int b = arguments[1].asNumber();
// 调用实际的 C++ 函数
int result = addNumbersNative(a, b);
// 将 C++ 结果包装成 jsi::Value 返回给 JS
return Value(runtime, result);
}
);
// 2. 将这个 HostFunction 注册到 JavaScript 的全局对象 (global) 上
// 或者挂载到全局对象下的某个命名空间 (例如 global.MyNativeModule)
runtime.global().setProperty(runtime, "addNumbersFromNative", std::move(addNumbersFunc));
}
} // namespace jsi
} // namespace facebook
在 JavaScript 侧,调用变得异常简单和直观:
// JavaScript 代码
// 假设 installMyJSIModule 已经执行
if (global.addNumbersFromNative) {
const sum = global.addNumbersFromNative(5, 3); // 同步调用!
console.log('Sum from native (synchronous):', sum); // 输出 8
// 可以立即使用 sum 进行后续操作
if (sum > 7) {
console.log('Sum is greater than 7!');
}
} else {
console.warn('addNumbersFromNative is not available.');
}
这段代码清晰地展示了 JSI 如何实现同步调用:JavaScript 调用 global.addNumbersFromNative(5, 3) 后,C++ 函数 addNumbersNative 会立即执行,并立即将结果 8 返回给 JavaScript。整个过程没有异步延迟,没有 JSON 序列化,就像调用一个普通的 JavaScript 函数一样。
4.3 JSI 如何取代 Bridge 的痛点
| 特性 | Bridge 架构 | JSI 架构 | 解决的痛点 |
|---|---|---|---|
| 通信方式 | 异步消息传递(JSON 序列化) | 直接函数调用(C++ 引用) | 异步延迟、UI 卡顿 |
| 数据传输 | 序列化/反序列化 JSON 字符串 | 直接参数传递、共享内存/引用 | 序列化开销、数据复制、内存浪费 |
| 调用类型 | 仅异步 | 同步和异步均可 | 无法实现实时交互、回调地狱 |
| 性能 | 存在序列化/反序列化瓶颈,较高延迟 | 接近原生调用性能,极低延迟 | 整体性能瓶颈 |
| 类型安全 | 运行时检查(弱类型) | 编译时类型安全(C++、结合 CodeGen) | 运行时错误、调试困难 |
| 内存管理 | 数据复制 | 共享内存、高效引用管理 | 内存冗余 |
JSI 是 Fabric 架构的基石,它不仅解决了 Bridge 架构带来的所有通信和性能问题,更重要的是,它为 React Native 赋予了与原生平台进行同步、低延迟交互的能力,这是实现原生级用户体验不可或缺的一环。
五、 Fabric 渲染器的核心管线与同步交互机制
现在我们已经理解了 JSI 如何提供同步通信的基础,接下来我们将深入探讨 Fabric 渲染器本身,它是如何利用 JSI 来构建和管理 UI,并实现与原生同步交互的。
5.1 Fabric 渲染器的核心组件再审视
在 Fabric 架构中,渲染过程主要围绕以下 C++ 核心组件展开:
-
C++ Shadow Tree (阴影树):
- 作用:这是一个在 C++ 内存中维护的、与 React 组件树结构相对应的树形数据结构。它存储了每个 React 组件的所有相关信息,包括其属性(props)、样式、布局指令,以及最重要的——布局计算的结果。
- 布局引擎:
Shadow Tree内部集成了Yoga布局引擎。Yoga是一个高性能的 C++ 布局库,实现了 Flexbox 布局规范。Shadow Tree利用Yoga在非主 UI 线程上高效地计算所有组件的最终尺寸和位置。 - 同步访问:这是实现同步交互的关键。由于
Shadow Tree存在于 C++ 内存中,JavaScript 可以通过 JSI 同步地查询任何Shadow Tree节点的属性或布局信息。
-
C++ UIManager:
- 作用:管理
Shadow Tree的生命周期,并负责将Shadow Tree中计算好的布局和属性更新,高效地“提交”到原生 UI 线程。 - 最小化变更集:
UIManager会比较当前Shadow Tree和上一次提交到原生 UI 线程的Shadow Tree之间的差异,计算出需要对原生视图进行的最小化变更集(创建、更新、删除视图)。
- 作用:管理
-
Host Views (原生视图):
- 作用:这些是最终呈现在屏幕上的实际原生 UI 组件(例如 iOS 上的
UIView、Android 上的android.view.View)。 Fabric渲染器不直接操作它们,而是通过UIManager向原生 UI 线程发送指令,由原生层来创建、配置和管理这些视图。
- 作用:这些是最终呈现在屏幕上的实际原生 UI 组件(例如 iOS 上的
5.2 Fabric 渲染管线的详细步骤
Fabric 的渲染管线是一个精心设计的流程,旨在最大限度地减少主 UI 线程的负担,并实现快速、同步的 UI 更新:
-
React Reconciliation (JavaScript 线程)
- 当 React 组件的状态或属性发生变化时,React 会在 JavaScript 线程中执行其协调算法(diffing 算法),生成一个新的虚拟 DOM 树(或者说是 React 元素的描述)。
- 产物:一系列描述 UI 变化的指令。
-
Shadow Tree Creation / Updates (JavaScript 线程 -> C++)
- React Reconciliation 发现 UI 变化后,会通过 JSI 同步地将这些变化(组件的类型、属性、样式等)传递给 C++ 层的
Fabric渲染器。 Fabric渲染器接收到这些指令后,会在 C++ 内存中构建或更新其Shadow Tree。每个 React 组件在Shadow Tree中都有一个对应的 C++ 节点。- 关键点:这一步是同步的。JavaScript 不需要等待任何异步回调,就能知道
Shadow Tree已经被更新。
- React Reconciliation 发现 UI 变化后,会通过 JSI 同步地将这些变化(组件的类型、属性、样式等)传递给 C++ 层的
-
Layout Calculation (C++ 布局线程)
- 一旦
Shadow Tree被更新,Fabric渲染器会触发Shadow Tree上的布局计算。 Yoga布局引擎会在一个独立的 C++ 布局线程(非主 UI 线程)上执行这些计算。这意味着即使布局计算非常复杂和耗时,也不会阻塞 JavaScript 线程或原生 UI 线程。- 产物:
Shadow Tree中的每个节点都将拥有其最终的尺寸(width, height)和位置(x, y)。
- 一旦
-
Commit to Native (C++ UIManager -> Native UI 线程)
- 布局计算完成后,C++ 层的
UIManager会将计算出的Shadow Tree提交到原生 UI 线程。 UIManager会比较当前Shadow Tree的状态与之前已渲染的原生视图树的状态,生成一个最小化的更新指令集(例如,“在位置 X, Y 处创建一个类型为 Text 的视图,文本内容为 Z”,“将 ID 为 V 的视图的宽度更新为 W”)。- 关键点:虽然最终的原生视图更新发生在原生 UI 线程,但从 C++
UIManager的角度来看,它向原生 UI 线程发送更新指令是同步的,并且这些指令是已经计算好、可以直接应用的,原生 UI 线程只需高效地执行即可。
- 布局计算完成后,C++ 层的
-
Mounting (Native UI 线程)
- 原生 UI 线程接收到
UIManager发送的指令集后,会高效地执行这些指令:创建新的原生视图、更新现有视图的属性(如尺寸、位置、文本内容、颜色)、删除不再需要的视图。 - 由于布局计算已经在 C++ 布局线程完成,原生 UI 线程的工作量被大大简化,只需执行简单的视图操作,从而确保 UI 更新的流畅性。
- 原生 UI 线程接收到
5.3 Fabric 如何实现与原生同步交互的能力
Fabric 渲染器的设计,尤其是 Shadow Tree 的存在和 JSI 的应用,是其实现与原生同步交互能力的核心。
-
同步查询 UI 布局和属性
- 机制:由于
Shadow Tree是一个 C++ 数据结构,并且 JavaScript 可以通过 JSI 同步访问 C++ 对象,这意味着 JavaScript 可以立即查询任何 React 组件对应的Shadow Tree节点的布局信息(如width,height,x,y)或其它属性。 - 优势:这彻底改变了旧架构中
UIManager.measure异步回调的模式。JavaScript 不再需要等待,可以立即获取所需信息并进行后续逻辑处理。 -
示例:
import { useRef, useEffect } from 'react'; import { View, Text } from 'react-native'; function MyComponent() { const viewRef = useRef(null); useEffect(() => { // 假设 Fabric 已经提供了一个同步的 measure API // 这个 API 在底层会通过 JSI 同步访问 Shadow Tree const measureSync = (nodeRef) => { // 这是一个概念性的API,实际名称和实现可能不同 // 但其核心是同步获取 Shadow Tree 信息 const { x, y, width, height } = nodeRef.current.getLayoutSync(); // 同步调用 console.log('View layout (synchronous):', { x, y, width, height }); return { x, y, width, height }; }; if (viewRef.current) { const layout = measureSync(viewRef); // 可以立即使用 layout 进行其他计算或更新 console.log('Immediately using layout data:', layout.width * 2); } }, []); return <View ref={viewRef} style={{ width: 100, height: 50, backgroundColor: 'blue' }} />; }在旧架构中,
measure必须是异步的,因为它需要通过 Bridge 与原生 UI 线程通信。而在Fabric中,如果Shadow Tree中已经包含了最新的布局信息,JavaScript 可以通过 JSI 同步地从 C++Shadow Tree中获取这些信息,而无需等待原生 UI 线程的实际渲染。
- 机制:由于
-
原生手势与动画的流畅响应
- 机制:
Fabric允许手势处理器和动画系统在 C++ 层更紧密地与原生事件循环集成。当用户执行手势时,原生事件可以直接被 C++ 层的手势处理系统捕获。 - 优势:这些手势可以直接在 C++ 层操作
Shadow Tree,甚至在某些情况下直接操作原生视图,实现几乎零延迟的响应。只有当手势完成后,或者需要复杂的逻辑判断时,才需要通知 JavaScript 线程。 - 示例:一个可拖拽的组件。用户开始拖拽时,
Fabric可以在 C++ 层捕获触摸事件,并直接在Shadow Tree中更新组件的x, y位置。这些位置变化会立即被提交到原生 UI 线程,实现元素的平滑跟随,而无需 JavaScript 线程的频繁介入。只有当拖拽结束,需要更新组件的state或执行业务逻辑时,才会通过 JSI 异步通知 JavaScript。
- 机制:
-
减少主 UI 线程阻塞
- 机制:将布局计算从 JS 线程和主 UI 线程剥离到 C++ 布局线程。
- 优势:即使应用包含复杂的布局,布局计算也不会阻塞主 UI 线程,从而确保 UI 的响应性和流畅性。原生 UI 线程只需要接收最终的、已计算好的指令并高效执行。
通过这些机制,Fabric 渲染器实现了 React Native 性能和交互体验的质的飞跃。它将 React Native 推向了真正意义上的“原生级”应用开发。
六、 TurboModules:原生模块的新范式
除了渲染器本身的变革,Fabric 架构还带来了对原生模块系统的彻底升级——TurboModules。TurboModules 利用 JSI 的强大能力,解决了旧 Bridge 架构下原生模块的诸多痛点。
6.1 TurboModules 的核心思想
TurboModules 的核心思想是:利用 JSI 实现 JavaScript 与原生模块之间直接、类型安全、可懒加载的同步或异步通信,彻底摆脱 Bridge 的序列化开销和异步限制。
主要特点:
- JSI 驱动:
TurboModules完全基于 JSI 构建,这意味着 JavaScript 可以直接持有原生模块的 C++ 代理对象,并调用其方法,无需通过 Bridge 进行 JSON 序列化和反序列化。 - 类型安全:
TurboModules引入了代码生成 (CodeGen) 机制。开发者需要通过 TypeScript 或 Flow 等接口定义语言 (IDL) 来描述原生模块的接口(方法名、参数类型、返回值类型)。CodeGen 工具会根据这些定义,自动生成 C++ 和 Java/Objective-C 的接口代码,以及 JavaScript 端的类型声明。这确保了编译时的类型一致性,减少了运行时错误。 - 懒加载 (Lazy Loading):旧的 Bridge 架构会在应用启动时加载并初始化所有原生模块。对于包含大量原生模块的应用来说,这会显著增加启动时间。
TurboModules实现了懒加载,原生模块只在第一次被 JavaScript 调用时才会被加载和初始化,从而加快了应用启动速度。 - 同步调用能力:对于某些不需要等待耗时操作的原生方法(例如,读取一个内存中的配置项),
TurboModules可以通过 JSI 实现同步调用,JavaScript 可以立即获得返回值。 - 减少 Boilerplate (样板代码):通过 CodeGen,开发者可以专注于编写原生业务逻辑,而无需手动编写大量的 JSI 绑定代码或 Bridge 兼容代码。
6.2 TurboModules 与旧原生模块的对比
让我们通过一个表格来清晰地对比 TurboModules 和旧原生模块系统:
| 特性 | 旧原生模块 (Bridge) | TurboModules (Fabric) |
|---|---|---|
| 通信方式 | 异步消息传递,JSON 序列化/反序列化 | 直接函数调用(通过 JSI),无需序列化 |
| 调用类型 | 仅异步(通过回调或 Promise 模拟同步) | 既支持异步,也支持同步(对于快速操作) |
| 类型安全 | 运行时检查,JS 和 Native 之间无编译时类型约束 | 编译时类型安全,通过 CodeGen 生成接口,强制类型匹配 |
| 加载机制 | 应用启动时加载所有模块 | 懒加载,模块在第一次被调用时才初始化 |
| 性能 | 存在序列化开销,通信延迟高 | 接近原生调用性能,通信延迟低 |
| 代码生成 | 无 | 强制使用 CodeGen 生成接口,减少手动绑定代码 |
| 内存开销 | 数据复制,JSON 字符串额外内存 | 共享内存或直接引用,内存开销更低 |
| 线程 | JS 线程调用 Native 线程,返回 JS 线程 | JS 线程直接调用 C++ 层(或其代理),结果直接返回 JS 线程 |
6.3 TurboModules 的实现流程(概念性)
-
定义接口 (IDL):开发者使用 TypeScript 或 Flow 接口文件
.jspec或.ts来定义TurboModule的接口。// MyAwesomeModule.ts interface Spec { getGreeting(name: string): Promise<string>; getConstantValue(): number; // 假设这个可以同步获取 sendEvent(eventName: string, data: Object): void; } export default Spec; -
代码生成 (CodeGen):React Native 的 CodeGen 工具会根据这个接口文件,自动生成:
- C++ 头文件和实现文件,用于定义
TurboModule的 C++ 接口。 - Java/Objective-C 的接口和基类,开发者需要继承并实现具体逻辑。
- JavaScript 端用于调用的类型定义和代理代码。
- C++ 头文件和实现文件,用于定义
-
实现原生逻辑:开发者在原生代码(Java/Kotlin for Android, Objective-C/Swift for iOS)中实现 CodeGen 生成的接口。
// Android: MyAwesomeModuleImpl.java import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.turbomodule.core.interfaces.TurboModule; // 概念性接口 public class MyAwesomeModuleImpl extends NativeMyAwesomeModuleSpec { // 继承生成的基础类 public MyAwesomeModuleImpl(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "MyAwesomeModule"; } @Override public Promise<String> getGreeting(String name) { return new Promise<>(() -> "Hello, " + name + " from Native!"); } @Override public double getConstantValue() { return 123.45; // 同步返回 } @Override public void sendEvent(String eventName, ReadableMap data) { // ... 发送原生事件给 JS } } -
注册
TurboModule:在 React Native 的原生初始化代码中,将TurboModule注册到JSI运行时。 -
JavaScript 调用:在 JavaScript 中,开发者可以通过
NativeModules或自动生成的导入方式,直接调用TurboModule的方法。import MyAwesomeModule from '@native-modules/MyAwesomeModule'; // 假设的导入路径 async function useMyAwesomeModule() { // 异步调用 const greeting = await MyAwesomeModule.getGreeting("World"); console.log(greeting); // "Hello, World from Native!" // 同步调用 const constant = MyAwesomeModule.getConstantValue(); // 同步获取 console.log("Constant value:", constant); // 123.45 // 发送事件 MyAwesomeModule.sendEvent("userLoggedIn", { userId: "123" }); }
通过 TurboModules,React Native 的原生模块系统变得更加现代化、高效和类型安全。它与 Fabric 渲染器一起,共同构建了新一代 React Native 架构的强大基石。
七、 新的事件系统:更直接、更高效
在 Fabric 架构下,事件系统也得到了显著改进,旨在减少事件处理的延迟,并提供更直接的交互反馈。
7.1 旧事件系统的局限性
在旧的 Bridge 架构中,原生 UI 线程捕获到的用户事件(如触摸、点击、滚动)需要经过以下流程才能到达 JavaScript 线程:
- 原生 UI 线程捕获事件。
- 事件信息被打包成 JSON 字符串。
- 通过 Bridge 异步发送到 JavaScript 线程。
- JavaScript 线程反序列化 JSON,并派发给 React 组件的事件处理器。
这个异步、序列化的过程会引入延迟,尤其对于高频事件(如触摸移动、滚动),这种延迟会导致 UI 响应不及时,用户体验不佳。
7.2 Fabric 事件系统的工作原理与优势
Fabric 的新事件系统利用了 JSI 和 C++ 层的能力,实现了更直接、更高效的事件流:
- C++ 层的事件处理:原生事件首先被 C++ 层的
Fabric渲染器捕获。 - 事件批处理:为了减少跨 JSI 的通信开销,
Fabric会对事件进行批处理。例如,一系列快速的触摸移动事件可能会被合并成一个批次,然后一次性传递给 JavaScript。 - JSI 直接派发:经过处理的事件数据通过 JSI 直接、高效地传递给 JavaScript 线程,而不是通过