跳到主要内容
预计阅读 38 分钟

ReactNative篇 | 新架构Fabric与TurboModule

前言

上一章我们拆解了 RN 老架构的 Bridge 模式,核心结论是:JSON 序列化 + 异步通信是所有性能问题的根源。Bridge 就像一座收费站,所有车辆(消息)都得排队、过检(序列化),高峰期一堵就全完。

那 Meta 团队是怎么解决这个问题的?答案是——干掉 Bridge

从 RN 0.68 开始,Meta 推出了全新的架构,核心由三大支柱组成:

  • JSI(JavaScript Interface):C++ 层的直接调用,取代 JSON Bridge
  • Fabric:全新的渲染系统
  • TurboModule:全新的 Native Module 系统

如果面试官问你”RN 新架构做了什么改进”,你不能只说”性能更好了”——你需要说清楚每个组件解决了什么问题、怎么解决的

本章就把这三大支柱以及配套的 Codegen,从原理到实战讲清楚。


诊断自测

Q1:JSI 是什么?它和 Bridge 最本质的区别是什么?

点击查看答案

JSI(JavaScript Interface)是一个用 C++ 编写的轻量级抽象层,允许 JavaScript 代码直接持有 C++ 对象的引用并同步调用其方法,无需经过 JSON 序列化和异步消息队列。与 Bridge 最本质的区别是:Bridge 是异步的、需要序列化的消息传递,而 JSI 是同步的、基于共享内存的直接调用

Q2:Fabric 和老的渲染系统相比,最关键的改进是什么?

点击查看答案

Fabric 最关键的改进有两点:

  1. 渲染管线可以同步执行:不再强制异步通过 Bridge,在需要时(如用户交互触发的高优更新)可以在 UI Thread 上同步完成 JS 调用 → 布局计算 → 视图更新的完整流程
  2. Shadow Tree 使用 C++ 实现并在多个线程间共享:JS Thread、UI Thread 可以直接访问同一份 C++ Shadow Tree,避免了跨线程的 JSON 数据复制

Q3:TurboModule 相比老的 Native Module,解决了哪些问题?

点击查看答案

TurboModule 解决了三个核心问题:

  1. 按需加载:不再启动时全量注册所有模块,而是首次使用时才加载,加快了启动速度
  2. 同步调用:通过 JSI 直接调用 Native 方法,不需要异步通过 Bridge
  3. 类型安全:通过 Codegen 生成类型化的接口代码,JS 和 Native 之间的参数类型在编译期就能检查

一、新架构全景

先从全局看看新架构长什么样,和老架构做个对比。

老架构 vs 新架构

老架构:
┌──────────┐                    ┌──────────┐
│ JS Layer │ ←── Bridge ────→  │  Native  │
│  (JSC)   │   (JSON/Async)    │  Layer   │
└──────────┘                    └──────────┘

新架构:
┌──────────┐                    ┌──────────┐
│ JS Layer │ ←── JSI(C++) ──→  │  Native  │
│ (Hermes) │   (直接引用/同步)   │  Layer   │
└──────────┘                    └──────────┘
        ↕                            ↕
   ┌────────────────────────────────────┐
   │      Shared C++ Layer              │
   │  (Fabric Renderer + TurboModule)   │
   └────────────────────────────────────┘

新架构的核心思路是引入一个 C++ 共享层,让 JS 和 Native 不再需要通过 Bridge 间接通信,而是通过 C++ 层直接交互。

这个变化带来的影响是全方位的:

  • 通信方式:从 JSON 异步变成 C++ 直接调用(同步可选)
  • 渲染系统:从老的 UIManager 变成 Fabric
  • Native Module 系统:从全量注册变成按需加载的 TurboModule
  • 类型系统:新增 Codegen,编译期生成类型化接口

二、JSI:新架构的基石

JSI 是整个新架构的基石。如果说老架构的核心是 Bridge,那新架构的核心就是 JSI。

2.1 JSI 是什么?

JSI(JavaScript Interface)是一个用 C++ 编写的轻量级接口层。它做的事情很简单但很关键:

让 JavaScript 代码可以直接持有 C++ 对象的引用,并同步调用这些对象上的方法。

用代码来理解:

// C++ 端:创建一个 HostObject
class MyModule : public jsi::HostObject {
public:
  jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override {
    if (name.utf8(rt) == "multiply") {
      return jsi::Function::createFromHostFunction(
        rt, name, 2,
        [](jsi::Runtime& rt, const jsi::Value& thisVal,
           const jsi::Value* args, size_t count) -> jsi::Value {
          double a = args[0].asNumber();
          double b = args[1].asNumber();
          return jsi::Value(a * b);
        });
    }
    return jsi::Value::undefined();
  }
};
// JS 端:直接调用,就像调用普通 JS 对象一样
const result = global.myModule.multiply(3, 4); // 12,同步返回!

注意这里的关键:JS 端调用 multiply 时,不需要序列化参数、不需要异步等待、不需要经过消息队列。它直接调用了 C++ 函数,C++ 函数直接返回结果。

2.2 JSI vs Bridge:本质区别

特性BridgeJSI
通信方式JSON 序列化 + 异步消息队列C++ 直接调用
同步/异步强制异步同步为主,异步可选
数据传递复制(序列化/反序列化)引用(共享内存)
JS 引擎耦合与 JSC 紧耦合抽象接口,引擎无关
类型安全无,运行时才能发现类型错误有,通过 Codegen 编译期检查

2.3 JSI 带来的额外好处:引擎无关

老架构中,Bridge 和 JavaScriptCore(JSC)是紧耦合的。想换一个 JS 引擎非常困难。

JSI 定义了一套抽象的 JavaScript 运行时接口,任何 JS 引擎只要实现了这套接口,就能接入 RN。这就是为什么 Hermes 引擎能够相对顺利地替换 JSC——因为它只需要实现 JSI 接口就行了。

JSI 接口(抽象层)
  ├── Hermes 实现
  ├── JSC 实现
  └── V8 实现(社区方案)

三、Fabric:新的渲染系统

Fabric 是 RN 新架构的渲染引擎,用来替代老架构的 UIManager。

3.1 老渲染系统的问题回顾

在老架构中,渲染流程大致是:

JS Thread: React reconciliation → 生成 UI 指令
    ↓ Bridge (JSON, 异步)
Shadow Thread: Yoga 布局计算
    ↓ Bridge (JSON, 异步)
UI Thread: 创建/更新原生视图

每一步之间都有 Bridge 的异步延迟。而且 Shadow Tree 只存在于 Shadow Thread 中,JS Thread 和 UI Thread 无法直接访问。

3.2 Fabric 的核心改进

Fabric 做了三件关键的事:

第一:用 C++ 实现 Shadow Tree,多线程共享

在 Fabric 中,Shadow Tree 是一个不可变(Immutable)的 C++ 数据结构。由于是 C++ 实现的,它可以在 JS Thread、UI Thread、Background Thread 之间直接共享,不需要通过 Bridge 复制数据。

┌─────────────┐
│ C++ Shadow  │ ← JS Thread 可以直接读写(通过 JSI)
│    Tree     │ ← UI Thread 可以直接读取
│ (Immutable) │ ← Background Thread 可以做布局计算
└─────────────┘

不可变(Immutable)设计保证了线程安全:每次更新不是修改原来的 Tree,而是创建一个新版本。类似 React 的 Immutable State 思路。

第二:支持同步渲染路径

老架构中,所有渲染都是异步的(因为要过 Bridge)。Fabric 支持多优先级渲染

  • 低优先级更新:仍然异步执行,不阻塞 UI 线程(类似老架构)
  • 高优先级更新(如用户交互触发):可以同步执行,在当前帧内完成 JS → 布局 → 渲染的完整流程

这和 React 18 的 Concurrent Features 是配合的。在 RN 新架构中,你可以利用 useTransitionuseDeferredValue 等 API 来区分更新优先级。

第三:Renderer 和平台解耦

Fabric 的渲染逻辑在 C++ 层实现,与具体的平台(iOS/Android)解耦。平台相关的部分只需要实现一个平台抽象层,告诉 Fabric 如何创建、更新、删除原生视图即可。

这使得 RN 更容易扩展到新平台(比如 VR、TV、Desktop)。

3.3 Fabric 渲染流程

以一次用户交互触发的 UI 更新为例:

1. [JS Thread]     用户事件触发 setState
2. [JS Thread]     React reconciliation,生成新的 React Element Tree
3. [C++ Layer]     通过 JSI 同步创建新的 Shadow Tree(Immutable)
4. [C++ Layer]     Yoga 布局计算(可在后台线程执行)
5. [C++ Layer]     Diff 新旧 Shadow Tree,生成变更列表
6. [UI Thread]     直接从 C++ 层读取变更列表,更新原生视图

和老架构对比,Bridge 的两次异步通信被完全消除了。JS Thread 通过 JSI 直接操作 C++ Shadow Tree,UI Thread 也直接从 C++ 层读取渲染指令。


四、TurboModule:新的 Native Module 系统

TurboModule 是新架构中替代老 Native Module 的方案。

4.1 老 Native Module 的问题

回顾一下老架构 Native Module 的三大问题:

  1. 启动时全量注册:即使你只用了 3 个模块,所有 100+ 个模块都要在启动时注册
  2. 异步调用:通过 Bridge 的 JSON 序列化,即使只是读取一个设备信息也是异步的
  3. 无类型安全:JS 端传什么参数,Native 端收到什么参数,中间没有任何校验

4.2 TurboModule 如何解决

按需加载(Lazy Loading)

// 老架构:启动时就注册好了
import { NativeModules } from 'react-native';
const camera = NativeModules.Camera; // 启动时已经存在

// 新架构:首次访问时才加载
import { TurboModuleRegistry } from 'react-native';
const camera = TurboModuleRegistry.get('Camera'); // 首次调用时才通过 JSI 加载

TurboModule 使用一个懒加载注册表。模块不在启动时全量初始化,而是在 JS 端第一次请求时,才通过 JSI 去初始化对应的 Native 模块。这对启动速度的提升非常显著,特别是大型项目中可能有几十上百个 Native Module。

同步调用

通过 JSI,JS 端可以同步调用 Native 方法并立即获取返回值:

// 老架构:必须异步
const version = await NativeModules.DeviceInfo.getVersion();

// 新架构:可以同步
const version = TurboModuleRegistry.get('DeviceInfo').getVersion(); // 同步返回

当然,不是所有方法都适合同步调用。耗时操作(如网络请求、文件 I/O)仍然应该是异步的。TurboModule 同时支持同步和异步两种模式,开发者可以根据场景选择。

类型安全(配合 Codegen)

这一点我们在下一节详细讲。

4.3 TurboModule 的工作原理

JS 端调用 camera.takePicture(options)
    ↓ JSI (C++ 直接调用,不经过 Bridge)
C++ TurboModule Wrapper
    ↓ 调用平台相关实现
Native Camera Module (ObjC / Java)
    ↓ 返回结果
C++ TurboModule Wrapper
    ↓ JSI (直接返回给 JS)
JS 端拿到结果

整个过程没有 JSON 序列化。参数通过 JSI 以 C++ 值类型或引用传递,返回值也是直接传回。


五、Codegen:类型代码自动生成

Codegen 是新架构的”基础设施”,为 Fabric 和 TurboModule 生成类型化的 C++ 接口代码。

5.1 为什么需要 Codegen?

在老架构中,JS 和 Native 之间的”契约”全靠开发者手动保持一致。你在 JS 端调用 NativeModules.Camera.takePicture({ quality: 'high' }) 时,如果 Native 端期望 quality 是一个数字而不是字符串,这个错误只能在运行时才发现。

Codegen 的思路是:用一份类型定义(TypeScript/Flow),自动生成 JS 端和 Native 端的接口代码,确保两边的类型完全一致。

5.2 Codegen 的工作流程

1. 开发者编写 TypeScript/Flow 类型定义(Spec 文件)

2. Codegen 在编译期解析类型定义

3. 生成 C++ 接口代码(供 Fabric/TurboModule 使用)
4. 生成 Native 端的接口代码(ObjC/Java)
5. 生成 JS 端的类型声明

一个 TurboModule 的 Spec 文件示例:

// NativeCamera.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  takePicture(options: {
    quality: number;
    flash: boolean;
  }): Promise<{
    uri: string;
    width: number;
    height: number;
  }>;

  getAvailableCameras(): Array<string>;  // 同步方法
}

export default TurboModuleRegistry.getEnforcing<Spec>('Camera');

Codegen 会根据这个 Spec 自动生成:

  • C++ 的 NativeCameraSpecJSI.h,定义了 C++ 层的接口
  • ObjC/Java 的抽象基类,Native 端实现时必须遵循
  • JS 端的类型声明

如果 Native 端的实现与 Spec 不一致(比如参数类型不对、缺少某个方法),编译期就会报错

5.3 Codegen 的意义

从工程角度看,Codegen 解决了一个长期困扰 RN 开发者的问题:JS 和 Native 之间的接口变更很容易出错。以前修改一个 Native Module 的接口,需要同时修改 JS 端和 Native 端,而且没有工具帮你检查是否改全了。现在只需要修改 Spec 文件,Codegen 自动保证两端同步。


六、新旧架构迁移策略

如果你的项目正在从老架构迁移到新架构,或者面试官问你”怎么迁移”,这部分内容是必须掌握的。

6.1 渐进式迁移

RN 新架构支持新旧架构共存。你不需要一次性把所有代码都迁移到新架构,而是可以渐进式地迁移:

  • 新写的模块用 TurboModule / Fabric Component
  • 老的模块暂时保留,通过互操作层(Interop Layer) 继续工作
  • 逐步将老模块改写为新架构

6.2 迁移步骤

以 TurboModule 为例,从老 Native Module 迁移的典型步骤:

1. 创建 TypeScript Spec 文件(定义模块接口)
2. 运行 Codegen,生成 C++ / ObjC / Java 接口代码
3. 修改 Native 端实现,继承 Codegen 生成的基类
4. 修改 JS 端调用方式(从 NativeModules 改为 TurboModuleRegistry)
5. 测试 + 验证

6.3 迁移中的常见问题

问题一:第三方库不支持新架构

这是目前迁移中最大的障碍。很多流行的第三方 Native Module 库还没有适配新架构。解决方案:

  • 检查库的 GitHub 是否有新架构适配的 PR 或分支
  • 使用 RN 的 Interop Layer,让老架构模块在新架构中运行
  • 考虑 fork 库自行适配

问题二:Hermes 兼容性

新架构推荐使用 Hermes 引擎(部分功能依赖 Hermes)。如果你的项目之前用的是 JSC,迁移时需要注意:

  • Hermes 不支持 with 语句、不支持某些 Proxy 的边缘用法
  • Hermes 的 Date 实现和 JSC 有细微差异
  • 部分 polyfill 在 Hermes 上行为可能不同

问题三:性能回归

迁移初期可能会出现部分场景性能反而下降。这通常是因为:

  • 新旧架构混用时,Interop Layer 本身有开销
  • 某些库的新架构实现还不够成熟
  • Codegen 生成的代码可能需要针对特定平台优化

6.4 是否应该立即迁移?

这取决于你的项目情况:

场景建议
新项目直接用新架构
老项目,第三方库已适配建议迁移
老项目,强依赖未适配的库等待库适配,或先用 Interop Layer
对性能不敏感的工具类 App迁移优先级低
有大量复杂动画/手势的 App优先迁移,收益最大

常见误区

误区一:“新架构完全去掉了异步通信”

不是。新架构的核心改进是提供了同步通信的能力,但不是说所有通信都变成了同步。耗时的 Native 操作(网络请求、文件 I/O、数据库查询等)仍然应该是异步的,否则会阻塞 JS Thread。JSI 给了你同步调用的选项,但什么时候该同步、什么时候该异步,是需要开发者自己判断的。

误区二:“新架构只是 Bridge 变快了”

新架构不是”更快的 Bridge”,而是一个完全不同的通信范式。Bridge 是消息传递模型(发送消息 → 等待处理 → 接收结果),JSI 是直接调用模型(直接调用函数 → 同步返回结果)。这两种模型的差异远超”快慢”的范畴——它影响了你能实现什么样的交互体验。比如在老架构中根本不可能实现的”JS 同步读取 Native 数据”,在新架构中就很自然。

误区三:“Fabric 就是在 Native 端实现了 Virtual DOM”

Fabric 的 Shadow Tree 和 React 的 Virtual DOM 不是一回事。Virtual DOM 是 React 在 JS 端维护的组件树表示,用于 Diff 算法。Shadow Tree 是 C++ 层的布局树,存储了每个节点的实际布局信息(位置、尺寸)。Fabric 的 Shadow Tree 更接近于浏览器的 Layout Tree / Render Tree,而不是 Virtual DOM。

误区四:“迁移到新架构就能解决所有性能问题”

新架构解决的是架构层面的性能瓶颈(Bridge 延迟、序列化开销、全量注册等)。但业务层面的性能问题(低效的 re-render、冗余的状态更新、复杂的组件嵌套等)仍然需要通过优化 React 代码来解决。新架构是”提高了天花板”,但你的代码质量决定了实际表现。


小结

本章从新架构的三大支柱出发,完整拆解了 JSI、Fabric、TurboModule 的设计原理和核心改进。

核心要点

  1. JSI:C++ 抽象层,允许 JS 直接持有 C++ 对象引用并同步调用,取代了 Bridge 的 JSON 异步通信
  2. Fabric:新渲染系统,C++ Immutable Shadow Tree 多线程共享,支持同步/异步多优先级渲染
  3. TurboModule:新 Native Module 系统,按需加载、同步调用、类型安全
  4. Codegen:根据 TypeScript Spec 自动生成 C++/ObjC/Java 接口代码,编译期保证类型一致
  5. 迁移策略:支持新旧架构共存,渐进式迁移

记忆口诀

新架构三支柱:JSI 管通道,Fabric 管渲染,TurboModule 管模块。Codegen 是裁缝,量好尺寸(Spec)再裁衣(生成代码)。


本章思维导图

RN 新架构
  • JSI(JavaScript Interface)
    • C++ 抽象层,取代 Bridge
    • JS 直接持有 C++ 对象引用
    • 同步调用,无需 JSON 序列化
    • 引擎无关(Hermes / JSC / V8)
  • Fabric(新渲染系统)
    • C++ Immutable Shadow Tree
    • 多线程共享(JS Thread / UI Thread / Background)
    • 同步渲染路径(高优先级更新)
    • 配合 React Concurrent Features
    • 平台解耦,易扩展
  • TurboModule(新 Native Module 系统)
    • 按需加载(Lazy Loading)
    • 同步调用(通过 JSI)
    • 类型安全(配合 Codegen)
  • Codegen(类型代码自动生成)
    • 输入:TypeScript / Flow Spec 文件
    • 输出:C++ / ObjC / Java 接口代码
    • 编译期类型检查
  • 迁移策略
    • 新旧架构共存(Interop Layer)
    • 渐进式迁移
    • 注意:第三方库适配、Hermes 兼容性
  • 新架构 vs 老架构
    • Bridge (JSON异步) → JSI (C++同步)
    • UIManager → Fabric
    • Native Module (全量注册) → TurboModule (按需加载)
    • 无类型安全 → Codegen

练习挑战

第一题 ⭐(基础):概念对比

请用一句话分别概括 JSI、Fabric、TurboModule 各自解决了老架构的什么问题。

点击查看答案
  • JSI:解决了 Bridge 的 JSON 序列化 + 异步通信问题——用 C++ 直接调用取代了消息传递。
  • Fabric:解决了老渲染系统中 Shadow Tree 无法跨线程共享、渲染强制异步的问题——用 C++ Immutable Shadow Tree + 同步渲染路径取代了老的 UIManager。
  • TurboModule:解决了 Native Module 启动时全量注册、调用必须异步、没有类型安全的问题——用按需加载 + JSI 同步调用 + Codegen 类型检查来取代。

第二题 ⭐⭐(进阶):场景分析

在 RN 新架构中,以下场景哪些适合使用同步调用,哪些仍然应该异步?请逐一分析。

  1. 读取设备的屏幕尺寸
  2. 发起一个 HTTP 网络请求
  3. 从 Native 的 SQLite 数据库查询 1000 条记录
  4. 获取当前系统的暗黑模式状态
  5. 调用原生的图片压缩模块
点击查看答案
  1. 屏幕尺寸 → 同步。数据已经在内存中,读取操作耗时极短(微秒级),同步返回即可。
  2. HTTP 请求 → 异步。网络请求的耗时是毫秒到秒级,如果同步调用会阻塞 JS Thread。
  3. 查询 1000 条数据库记录 → 异步。数据库 I/O 是耗时操作,而且 1000 条数据的序列化/传递也需要时间。
  4. 暗黑模式状态 → 同步。这是一个简单的状态读取,数据在内存中,微秒级操作。
  5. 图片压缩 → 异步。图片压缩是 CPU 密集型操作,可能耗时几百毫秒,必须异步以免阻塞 JS Thread。

判断标准: 如果操作耗时在微秒级、数据已在内存中,用同步;如果涉及 I/O、网络、CPU 密集计算,用异步。

第三题 ⭐⭐⭐(综合):编写 TurboModule Spec

假设你需要创建一个 TurboModule 用于管理用户偏好设置(UserPreferences),支持以下功能:

  1. 同步获取某个 key 的 string 值
  2. 异步保存 key-value 对
  3. 同步获取所有 key 的列表
  4. 异步清除所有偏好设置

请编写 TypeScript Spec 文件。

点击查看答案
// NativeUserPreferences.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  // 同步方法:读取内存中的值,耗时极短
  getString(key: string): string | null;

  // 异步方法:写入磁盘,涉及 I/O
  setString(key: string, value: string): Promise<void>;

  // 同步方法:返回内存中的 key 列表
  getAllKeys(): Array<string>;

  // 异步方法:清除磁盘数据,涉及 I/O
  clear(): Promise<void>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('UserPreferences');

设计思路:

  • getStringgetAllKeys 设为同步,因为偏好设置通常会缓存在内存中,读取操作非常快
  • setStringclear 设为异步(返回 Promise),因为写入和清除操作涉及磁盘 I/O,不应阻塞 JS Thread
  • 方法命名遵循了 TurboModule 的惯例,接口继承 TurboModule
  • 使用 TurboModuleRegistry.getEnforcing 而不是 get,确保模块不存在时直接报错而不是返回 null

自我检测

读完本章后,对照下面的清单检验一下自己的掌握程度:

  • 能画出新架构的整体结构图,并标注 JSI、Fabric、TurboModule 各自的位置
  • 能用自己的话解释 JSI 是什么,以及它和 Bridge 在通信模型上的本质区别
  • 能说出 Fabric 的三个核心改进(C++ Shadow Tree 共享、同步渲染路径、平台解耦),并解释每个改进解决了什么问题
  • 能说清楚 TurboModule 的按需加载、同步调用、类型安全三大特性
  • 能解释 Codegen 的作用和工作流程:输入什么、输出什么、解决什么问题
  • 能判断一个 Native 方法应该设计成同步还是异步,并说出判断标准
  • 能说出新旧架构迁移的大致步骤和可能遇到的问题
  • 能在面试中用”老架构问题 → 新架构方案”的链路,完整回答”RN 新架构做了什么改进”这类问题

购买课程解锁全部内容

大厂前端面试通关:71 篇构建完整知识体系

¥89.90