React Native 新架构(JSI + Fabric + TurboModules + Hermes)是对 2015 年旧架构根本性的重写,核心驱动力是:用直接内存访问替代 JSON 序列化的异步消息队列,从根本上消灭旧 Bridge 带来的性能天花板。本页从旧架构的具体痛点出发,逐层拆解新架构每个组件的设计动机。
概述:旧架构到底有什么问题?
要理解新架构,必须先理解旧架构——不是因为旧架构”不好”,而是它在 2015 年是一个合理的工程决策,但在 2020 年代已经撑不住了。
旧架构的三线程模型
旧架构把整个 RN 运行时拆成三条线程,彼此通过一个叫 Bridge 的消息总线通信:
┌─────────────────┐ JSON 消息队列 ┌─────────────────┐
│ JS Thread │ ──────────────────► │ UI Thread │
│ │ │ (主线程) │
│ React 渲染树 │ ◄────────────────── │ 原生视图操作 │
│ 业务逻辑 │ JSON 消息队列 │ 用户输入事件 │
└─────────────────┘ └─────────────────┘
│ │
│ JSON 消息队列 │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Shadow Thread │
│ (也叫 Layout Thread / Yoga Thread) │
│ 负责运行 Yoga 布局引擎,把 React 的 flexbox 计算 │
│ 转成原生视图的具体坐标和尺寸 │
└─────────────────────────────────────────────────────────┘
三条线程各司其职:
- JS Thread:运行你写的 JavaScript 代码,React diff、状态更新、事件处理器,全在这里。
- UI Thread(主线程):操作原生视图(
UIViewon iOS,Viewon Android),任何渲染都必须在这里执行。 - Shadow Thread:运行 Yoga 布局引擎,把 React 的 flexbox 规则计算成
{x, y, width, height}坐标,再把结果传给 UI Thread 去渲染。
这个设计的好处是:JS 不阻塞 UI,JS 崩了主线程还在。问题是,三条线程沟通必须经过 Bridge——而 Bridge 有三个硬伤。
Bridge 的三个具体痛点
痛点 1:JSON 序列化开销
Bridge 本质上是一个异步消息队列。JS 侧要调用一个 Native 方法时,不能直接调用,必须:
- 把参数序列化成 JSON 字符串
- 把 JSON 字符串放进队列
- Native 侧从队列取出
- JSON 反序列化成 Native 数据结构
对于「每帧 60 次的滚动事件」、「动画的每一帧位置更新」,每秒 60 次 × 序列化/反序列化,开销积累非常可观。
痛点 2:异步延迟带来的动画掉帧
旧架构里,JS Thread 控制动画逻辑,每一帧都要通过 Bridge 把新的位置值发给 UI Thread。Bridge 是异步的——当 JS Thread 被任何业务逻辑占用(比如处理一个大列表的 state 更新),动画帧就会饿死,直接掉帧。这也是为什么早期 RN 项目「用 JS 写动画很难丝滑」。
痛点 3:单通道、无类型的消息队列
所有 JS 和 Native 的通信共用同一条 Bridge。不管是事件处理、布局计算、还是 Native 模块调用,全部挤在一条队列里。任何一个调用量大的地方都可能让整条通道堵塞。更糟的是,消息格式是 JSON,没有类型信息。改参数名会在运行时静默失败,没有编译期保障。
历史背景:架构演进时间线
| 年份 | 事件 |
|---|---|
| 2015 | React Native 开源,Bridge 架构随之诞生。当时主要目标是「让 JS 可以驱动原生 UI」,Bridge 是最直接的实现方式。 |
| 2018 | Meta 内部开始 JSI(JavaScript Interface)RFC。核心动机:能不能让 JS 直接持有 Native 对象的引用,跳过 JSON 序列化? |
| 2019 | Hermes 引擎开源。Meta 内部数据显示 App 启动时间中有大量时间花在 V8/JSC 的 JIT 预热上,Hermes 用字节码预编译解决这个问题。 |
| 2021 | 新架构进入 beta,JSI / Fabric / TurboModules 同步推进。Fabric 重写渲染器,TurboModules 重写 Native 模块系统。 |
| 2022 | RN 0.68 起允许「opt-in」启用新架构,但默认关闭。生态库还不完全兼容。 |
| 2024 | RN 0.76 新架构默认开启。Meta 在内部 App(Facebook、Instagram)验证了足够的稳定性。Expo SDK 52+ 跟进。 |
| 2025 | Expo SDK 54(MindGym 使用的版本),RN 0.81/0.82,新架构是默认状态,旧架构支持进入 deprecation 轨道。 |
工作方式
JSI:跳过 Bridge 的关键
JSI 全名 JavaScript Interface,是新架构最核心的基础设施。
旧架构的 Bridge 为什么必须用 JSON?因为 JS 引擎(V8 / JavaScriptCore)和 Native 代码(C++ / ObjC / Java)运行在完全不同的内存空间里。JS 的对象活在 JS 堆里,Native 无法直接访问,反之亦然。Bridge 用 JSON 做「中间格式」,本质上是把两个内存世界之间的传输问题转成了字符串序列化问题。
JSI 的解法:Host Object
JSI 引入了一个概念叫 Host Object——一个活在 C++ 里、但可以被 JS 直接引用的对象:
JS 侧:
const mmkv = global.__mmkvInstance // 这不是普通的 JS 对象
mmkv.set('key', 'value') // 这个调用直接触发 C++ 代码,同步返回
C++ 侧:
// MMKV 注册了一个 Host Object,实现了 get/set 方法
// JS 调用 mmkv.set() 时,JS 引擎通过 JSI 直接调用 C++ 的实现
关键词:同步、无序列化、直接内存访问。
JSI 让 JS 可以持有 Native 对象的引用(不是副本),调用时不经过任何队列,返回值立即可用。这从根本上消灭了 Bridge 的三个痛点。
这也是为什么 MindGym 技术栈里选了 react-native-mmkv:MMKV 完全基于 JSI 实现,mmkv.getString('key') 是同步调用,不需要 await,读写性能比基于旧 Bridge 的 AsyncStorage 快约 30 倍。
Hermes:为 RN 定制的 JS 引擎
RN 旧架构用的是 JavaScriptCore(iOS 自带)或 V8(Android 可选)。这两个引擎是为浏览器设计的——它们假设你的代码会长时间运行,值得花时间做 JIT 编译优化。
移动 App 的场景完全不同:用户点开 App,3 秒内如果白屏就会关掉。JIT 预热需要时间,而这段时间恰好在 App 启动阶段——用户最敏感的时刻。
Hermes 的解法:字节码预编译
Hermes 在构建期(eas build 的时候)把你的 JS 代码编译成 Hermes 字节码(.hbc 文件),打进 App bundle 里。App 启动时,引擎直接执行字节码,跳过解析和编译阶段。
旧流程: App 启动 → 读取 JS 源码 → 词法分析/语法分析 → AST → 字节码 → 执行
新流程: App 启动 → 读取已编译的字节码 → 直接执行
Meta 的数据(2019 年 Hermes 发布博文):启用 Hermes 后,Android 的 TTI(Time to Interactive)从 4.3 秒降到 2.0 秒,内存占用下降约 40%,APK 体积下降约 6%。
Hermes 从 Expo SDK 48 / RN 0.71 起成为 iOS 和 Android 的默认引擎。
Fabric:重写渲染器
旧架构的渲染器叫 Paper,Shadow Thread 里跑着用 JS 实现的 Yoga 布局引擎,通过 Bridge 和 UI Thread 通信。
Fabric 的变化
Fabric 把整个渲染管线从 JS 移到了 C++:
- Shadow Tree 全量 C++ 实现:布局计算、虚拟 DOM diff,全部在 C++ 里完成,不再有 JS 参与渲染核心路径。
- 同步布局测量:旧架构里,获取一个视图的尺寸要异步——发消息,等回调。Fabric 里,
measure()可以同步返回,因为 C++ Shadow Tree 就在同一个内存空间里。 - 支持 Concurrent Rendering:React 18 的 Concurrent Mode(并发渲染)需要渲染器支持「可中断、可恢复」的渲染。Paper 的架构不支持,Fabric 从设计上兼容。这意味着未来
startTransition、Suspense在 RN 里可以真正发挥作用。
直观类比:Paper 像是两座城市之间用电报通信,Fabric 是两座城市合并成一座城,本地通话。
TurboModules:懒加载 + 类型安全的 Native 模块
旧架构的 Native Modules 系统有一个问题:所有模块在 App 启动时全部初始化,哪怕你只用了其中 5 个。想象一下,你 import 了一个照相机模块,即使用户没有点击拍照,App 启动时相机相关的所有 Native 代码就已经加载并初始化了。
TurboModules 的改进
- 懒加载:模块只在第一次被 JS 访问时才初始化。未用到的模块不占启动时间。
- 类型安全:TurboModules 引入了 Codegen。你写一份 TypeScript 接口声明,
npx react-native codegen会自动生成 iOS(ObjC/Swift)和 Android(Java/Kotlin)侧的对应接口代码。改参数类型,编译期就会报错,不再等到运行时发现。 - 基于 JSI:TurboModule 的调用走 JSI,不走 Bridge,同步可调用。
// 假设你在定义一个自定义 TurboModule(MindGym 目前不需要,但这是原理)
// 你在 TS 侧声明:
interface Spec extends TurboModule {
getRandomSeed(): number; // 同步
saveRecord(data: SessionRecord): Promise<void>; // 异步
}
// Codegen 会根据这份声明生成 iOS/Android 的接口文件
// Native 侧实现这些接口,类型由编译器保证
Bridgeless:终点站
Bridgeless 是新架构的最终状态:完全移除 Bridge 相关代码。
在「新架构 + 仍有 Bridge」的过渡期(RN 0.68-0.75),Bridge 代码还保留着,旧的 Native Modules(不支持 TurboModules 的库)还能靠 Bridge 跑。Bridgeless 模式下,所有通信必须走 JSI / Fabric / TurboModules,Bridge 代码从 App bundle 里彻底删除。
global.RN$Bridgeless 是 RN 注入到 JS 全局对象的一个 boolean,当且仅当 App 运行在真正的 Bridgeless 模式下时为 true:
// 在任意 React 组件或 .ts 文件里
if (global.RN$Bridgeless === true) {
console.log('真正的 Bridgeless 模式,Bridge 已移除')
} else {
console.log('Bridge 仍然存在(过渡期或旧架构)')
}
旧架构 vs 新架构对比
| 维度 | 旧架构(Bridge / Paper) | 新架构(JSI / Fabric / TurboModules) |
|---|---|---|
| JS↔Native 通信 | 异步 JSON 消息队列 | JSI 同步直接调用 |
| 序列化开销 | 每次通信都要 JSON 序列化/反序列化 | 无序列化,直接内存引用 |
| Native Modules 加载 | App 启动时全部初始化 | 懒加载,用到时才初始化 |
| 类型安全 | 无(约定驱动,运行时才知道错) | Codegen 从 TS 生成接口,编译期保障 |
| 渲染器 | Paper(JS + C++ 混合,Bridge 通信) | Fabric(全量 C++,同步布局) |
| 布局测量 | 异步(发消息等回调) | 同步(C++ 直接返回) |
| Concurrent Rendering | 不支持 | 支持(React 18 并发特性可用) |
| JS 引擎 | JSC / V8(JIT 为主) | Hermes(字节码预编译,低启动延迟) |
代码示例:MindGym 里我们在哪里验证新架构
1. app.json 启用新架构
// /Users/hat_cloud/Projects/MindGym/MindGymApp/app.json
{
"expo": {
"newArchEnabled": true, // 这一行告诉 Expo 构建系统启用新架构
...
}
}
newArchEnabled: true 在 Expo SDK 54 里做了两件事:
- iOS:在 Podfile 里写入
ENV['RCT_NEW_ARCH_ENABLED'] = '1',CocoaPods 安装时启用 Fabric + TurboModules - Android:在
gradle.properties里写入newArchEnabled=true,Gradle 构建时启用新架构
2. 运行时检测 global.RN$Bridgeless
// 例如放在 app/_layout.tsx 或任意启动早的位置
// 仅用于开发期调试,生产环境不需要这行
if (__DEV__) {
console.log(
'新架构状态:',
global.RN$Bridgeless === true ? 'Bridgeless ✓' : '仍有 Bridge'
)
}
在 Expo SDK 54 + 新架构默认开启的环境下,这行日志应该打出 Bridgeless ✓。
3. MMKV 是 JSI 的活教材
import { MMKV } from 'react-native-mmkv'
const storage = new MMKV()
// 同步读写,不需要 await
storage.set('streak', 5)
const streak = storage.getNumber('streak') // 立即返回,不是 Promise
如果用旧架构的 AsyncStorage,同样的操作需要:
await AsyncStorage.setItem('streak', JSON.stringify(5))
const raw = await AsyncStorage.getItem('streak')
const streak = raw ? JSON.parse(raw) : null
异步、需要序列化、需要错误处理——这是 Bridge 模型的直接后果。
面试视角
“说一下 RN 新架构”
抓手:旧架构 Bridge 的三个具体问题(序列化开销、异步延迟、无类型)→ 新架构用 JSI 替换 Bridge,用 Fabric 重写渲染器,用 TurboModules 重写 Native 模块系统,用 Hermes 替换 JS 引擎。不要背名词,要说清楚「每个组件解决了什么具体问题」。
“JSI 是什么?”
JSI 是让 JS 可以直接持有 C++ 对象引用的接口层。核心机制是 Host Object——一个活在 C++ 里但可以被 JS 直接操作的对象,调用时同步返回,无 JSON 序列化。和 Bridge 的本质区别在于:Bridge 是「两个进程用字符串通信」,JSI 是「同一个进程里 JS 直接调用 C++」。
“Fabric 和 Paper 的区别?”
Paper 是旧渲染器,Shadow Tree 用 JavaScript 维护,和 UI Thread 之间通过 Bridge 异步通信。Fabric 把 Shadow Tree 全量移到 C++,和 UI Thread 同步通信,消灭了布局测量的异步延迟。更重要的是,Fabric 支持 React 18 的 Concurrent Rendering,Paper 的架构在设计上就不支持。
“TurboModules 和 Native Modules 的区别?”
两个核心差异:① 懒加载,Native Modules 启动时全部初始化,TurboModules 按需加载;② 类型安全,Native Modules 靠约定,TurboModules 通过 Codegen 从 TS 生成接口,编译期保障类型正确。底层通信也从 Bridge 换成了 JSI。
“Bridgeless 是什么?”
Bridgeless 是新架构的最终形态——Bridge 代码从 App 里完全移除。在新架构过渡期(0.68-0.75),Bridge 还保留用于兼容老库。Bridgeless 完全切断这条退路,强制所有通信走 JSI。global.RN$Bridgeless === true 是运行时判断是否处于 Bridgeless 模式的方式,Reanimated、Expo 等库内部都在用这个标志。
版本说明
本页基于 MindGym M0 阶段(Expo SDK 54, RN 0.81.5, 2026-04-13)。
参见
参考
- RN 官方新架构文档(重点看「Architecture Overview」和「Pillars」章节)
- JSI 原始 RFC(2018) — 看原始 issue 里 React Native 团队的讨论,能感受到当时做这个决策的真实动机和争议
- Meta 新架构发布博文(2022) — Meta 内部 App 的实际数据和迁移经验