口述:RN新旧架构对比深度解析
多维度对比 RN 旧架构(Bridge+Paper)与新架构(JSI+Fabric+TurboModules),一张表看懂十年通信范式的演进。
一句话概括
RN 新旧架构的本质区别:通信从”异步 JSON 消息队列”变成”同步 C++ 函数调用”,渲染从”UI 线程一个人干所有活”变成”三条线程各司其职的流水线”,模块从”启动全量注册”变成”按需懒加载”。
核心知识点
1. 一张对比表看清两代架构
| 维度 | 旧架构 (Bridge + Paper) | 新架构 (JSI + Fabric) |
|---|---|---|
| JS↔原生通信 | 异步 JSON 消息队列 | 同步 JSI 宿主对象 |
| 单次通信延迟 | 0.5-3ms(含序列化+排队) | ~0.01ms(函数指针) |
| 布局计算 | UI 线程跑 Yoga | 独立 Shadow 线程 |
| 线程模型 | 实际两条(JS + UI 兼布局) | 三条(JS/Shadow/UI) |
| Native Module | 启动时全量初始化 | 按需懒加载 |
| 模块注册方式 | 反射 + @ReactMethod | Codegen 编译时生成 |
| JS 引擎 | JSC(固定) | 可插拔(Hermes/JSC/V8) |
| 列表渲染 | 每次 diff 重建 View | ShadowNode 复用 |
2. 通信演进:理解”为什么异步是个多余的抽象”
1
2
3
4
5
6
7
8
9
10
// 旧架构:JS 想调一个简单的原生方法
NativeModules.Storage.get('key', (val) => console.log(val))
// 背后发生:参数序列化 → 入队 → 等 flush → JNI 跨语言 → JSON.parse →
// 反射找方法 → 执行 → 序列化结果 → 排队回传 → JS 回调
// 7 个步骤,每一步都有隐性开销
// 新架构:同样的调用,JSI 版本
const val = NativeModules.Storage.get('key')
// 背后发生:JSI 找 HostObject → 调函数指针 → 返回
// 2 个步骤,没有序列化、没有队列、没有反射
核心洞察:JS 和原生代码跑在同一个进程里,为什么通信要像跨网络一样”序列化—传消息—反序列化”?JSI 纠正了这个过度抽象——都在同一地址空间,函数指针完全可以直接调用。
3. 渲染演进:从”一人干所有活”到”流水线分工”
1
2
3
4
5
6
7
旧架构 Paper 渲染器:
JS 线程 Diff → Bridge → UI 线程(布局+绘制 全包)
↑ 被布局计算拖死就无法响应触摸
新架构 Fabric 渲染器:
JS 线程 Diff → JSI → Shadow 线程(纯布局)→ 原子提交 → UI 线程(纯绘制)
↑ 跑再慢也不影响 UI 响应 ↑ 只负责创建 View
原子提交是精髓:不是每改一个属性通知一次 UI 线程,而是把一轮的所有变更打包一起提交。UI 线程醒来一次处理一批连续操作,减少被唤醒的次数。
4. 模块演进:懒加载的革命
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 旧架构:getPackages() 返回所有模块,启动时全部初始化
// 50 个模块 → 200-500ms 启动耗时
public List<NativeModule> createNativeModules(ReactApplicationContext ctx) {
return Arrays.asList(
new StorageModule(ctx), // 用户可能永远不用的模块也要加载
new BluetoothModule(ctx),
new CameraModule(ctx),
// ... 50 个模块
);
}
// 新架构:TurboModule 懒加载
// JS 侧首次 import 时才通过 JSI 初始化
const Camera = TurboModuleRegistry.get('Camera')
// 这一行之前,Camera 模块零开销
Codegen 补上了最后一环:旧架构的 @ReactMethod 靠运行时反射调用(慢+类型不安全),Codegen 在编译时生成 C++ 桥接代码,编译期就能发现类型不匹配。
5. 新架构的”双刃剑”:同步调用的风险
1
2
3
4
// ⚠️ 新架构里这行代码的危险程度比旧架构高得多
const result = NativeModules.ImageProcessor.processImage(base64Image)
// 如果 processImage 在 C++ 的 HostFunction 里做了耗时同步操作
// JS 线程直接卡死!旧架构至少异步不会阻塞 JS
正确的 TurboModule 实现:耗时操作必须在原生侧派发到后台线程,方法本身尽快返回 Promise,让 JS 线程继续处理其他事件。
其实你每天都在用
- 旧架构的
debug mode是最直观的性能对比 — JS 跑在电脑 Chrome 里,Bridge 还要加一层 WebSocket,通信多走一整趟网络往返。debug 和 release 的性能差距本身就是 Bridge 开销的放大镜 useNativeDriver: true是新架构理念的提前落地 — 动画不经过 JS 线程在原生侧跑,这就是 Fabric 对整个渲染过程想做的事情- RN 0.73+ 的 Interop Layer 就在你项目里 — 它让旧 NativeModule 在新架构上能跑,但这个兼容层本身就是 Bridge 的”回退通道”,性能并不好
requireNativeComponent在新架构里会走codegenNativeComponent— 组件注册方式已经变了,只是你用的库帮你封装了- Hermes 引擎 + JSI 是深度绑定关系 — Hermes 不是”一个更快的 JSC”,它是为新架构设计的 JS 引擎,对 JSI HostObject 有原生级别的优化
常见误解(FAQ)
❌ 误区:「新架构一定比旧架构快」 对于静态内容页、少交互页面,旧架构和新架构的性能差异几乎无感。新架构真正拉开差距的场景是高频交互——手势拖拽、快速滚动列表、实时动画。如果把所有页面都从旧架构迁移到新架构,ROI 最高的是那些”用户频繁触控”的页面。
❌ 误区:「升级到新架构后项目什么都不用改」 Native Module 要从 @ReactMethod 注解改为 TurboModule 的 Codegen 接口,自定义 ViewManager 要适配 Fabric 的 ComponentDescriptor。RN 官方提供了 Interop Layer 兼容层,但不是零成本——兼容层的模块仍然走 Bridge 逻辑,性能没提升。
❌ 误区:「新架构的同步调用 = 可以像同步代码一样肆无忌惮地调原生方法」 JSI 同步只是”调用方式同步”,但原生方法做的是耗时操作(网络请求、文件读写)——如果没做线程分流,JS 线程会被直接阻塞。这一点新架构比旧架构更危险:旧架构异步至少自动防阻塞。
❌ 误区:「迁移后所有 Bridge 问题都没了」 Fabric 没有完全消灭异步——JS 线程通知 Shadow 线程”该布局了”仍然是异步消息(线程间调度)。只是异步的频率从”每次通信”降到了”每帧一次”,延迟大幅降低但没归零。
一句话总结
RN 从旧架构到新架构不是”修了个 bug”,而是重新回答了一个基础问题——既然 JS 和原生跑在同一个进程里,为什么它们之间的通信要像两个服务器一样走消息队列?JSI 的回答是:不需要。