文章

口述:RN新旧架构对比深度解析

多维度对比 RN 旧架构(Bridge+Paper)与新架构(JSI+Fabric+TurboModules),一张表看懂十年通信范式的演进。

口述:RN新旧架构对比深度解析

一句话概括

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启动时全量初始化按需懒加载
模块注册方式反射 + @ReactMethodCodegen 编译时生成
JS 引擎JSC(固定)可插拔(Hermes/JSC/V8)
列表渲染每次 diff 重建 ViewShadowNode 复用

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 线程继续处理其他事件。

其实你每天都在用

  1. 旧架构的 debug mode 是最直观的性能对比 — JS 跑在电脑 Chrome 里,Bridge 还要加一层 WebSocket,通信多走一整趟网络往返。debug 和 release 的性能差距本身就是 Bridge 开销的放大镜
  2. useNativeDriver: true 是新架构理念的提前落地 — 动画不经过 JS 线程在原生侧跑,这就是 Fabric 对整个渲染过程想做的事情
  3. RN 0.73+ 的 Interop Layer 就在你项目里 — 它让旧 NativeModule 在新架构上能跑,但这个兼容层本身就是 Bridge 的”回退通道”,性能并不好
  4. requireNativeComponent 在新架构里会走 codegenNativeComponent — 组件注册方式已经变了,只是你用的库帮你封装了
  5. 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 的回答是:不需要。

本文由作者按照 CC BY 4.0 进行授权