React Native 运行时深度解析

当你第一次用React Native写完一个按钮,点击后看到原生界面乖乖响应时,可能会惊叹:"JavaScript居然能指挥原生控件?"这种魔法般的体验,让无数开发者爱上RN。但当你 profiling 应用时发现莫名其妙的白屏,或列表滚动掉帧时,又会困惑:"我的JS代码到底在哪执行?它和原生UI之间隔着什么?"这些问题的答案,都藏在RN运行时的三线程通信、Bridge序列化与JSI直接调用的细节里。本文将抛开JSX与样式表,带你深入C++层,看清每一帧渲染如何跨越语言边界,以及新架构如何让"JS写原生"真正飞起来。

一、核心架构概览

React Native 运行在三个主要线程 上:

二、旧架构:Bridge 机制(Legacy Architecture)

先讲一下旧架构,即Bridge机制

1. 工作原理

Bridge 是 React Native 0.68 之前 的核心,采用异步序列化通信

那何为异步序列化呢,以下有几个概念:

  • 异步性:所有调用都是异步的,无法同步返回复杂数据

  • 序列化开销:大数据量通信时性能瓶颈明显

  • 单通道:所有通信挤在一个队列,容易阻塞

三、新架构:JSI + Fabric + TurboModules

1. JSI (JavaScript Interface)

JSI 是 C++ 层,提供了直接持有原生对象引用的能力

javascript 复制代码
// C++ 侧暴露原生对象
class NativeModule {
public:
  void showToast(jsi::String message) {
    // 直接调用原生 API
  }
};

// JS 侧直接同步调用
const nativeModule = global.__turboModuleProxy('ToastAndroid');
nativeModule.showToast('Hello'); // 同步执行!

核心优势

  • 同步调用:支持同步返回复杂对象

  • 对象引用:JS 直接持有 C++ 对象指针,无需序列化

  • 动态绑定:运行时动态加载模块,启动更快

2. Fabric 渲染系统

Fabric 是新的 UI 渲染器,取代旧的 UI Manager

单说概念比较抽象,举两个例子:

旧渲染模型 :JS线程算完布局 → 写信 序列化 成 JSON → 寄到 Shadow 线程排好版 → 再寄到 UI 线程画出来。全程异步单通道,一步堵步步堵。

新渲染模型 :JS 通过 JSI 直接给 C++ 层打电话 → C++ 一边算布局一边指挥 UI 线程画。同步调用,两边同时干活,不绕弯子。

流程对比图

总感觉我举得栗子还是比较抽象,让AI帮我们想一个例子

旧架构(像邮寄信件)

scss 复制代码
JS线程:画好图纸 → ✉️ 打包邮寄 →  Shadow线程:量尺寸排版 → ✉️ 再寄 → UI线程:施工盖楼
          (序列化)      等3-5帧              (Yoga)          等3-5帧         (60ms+)

新架构(像视频通话+无人机指挥)

scss 复制代码
JS线程:图纸直接投屏到 C++ 层 → C++:实时算尺寸并指挥 → UI线程:同步施工
          (JSI对象引用)           (共享内存)                   (20ms内)

核心区别:老架构把数据当"包裹"传来传去,新架构让 JS 直接"捏"着原生对象的遥控器,省掉中间商赚差价。

Fabric 的关键改进

  • C++ 层统一布局:Yoga 在 C++ 运行,跨平台一致性更好

  • 异步优先级渲染:支持 React 18 的并发特性

  • 更短的渲染路径:减少线程切换

3. TurboModules

TurboModules 是懒加载的原生模块系统

旧架构 Native Modules有个很大的槽点,就是启动就全加载 :不管用不用,所有原生模块(相机、定位、蓝牙等)在 App 启动时一次性初始化,像开机自启 50 个软件,启动慢。

而新架构有了TurboModules,支持懒加载,大大降低启动时间,降低内存占用

dart 复制代码
// 自动生成的 C++ 接口
struct ToastAndroidSpec : TurboModule {
  void show(String message, int duration);
};

// JS 侧按需加载,首次调用时才初始化
const Toast = TurboModuleRegistry.get('ToastAndroid');
Toast.show('Lazy loaded!'); // 启动时不加载,减少启动时间

四、启动流程详解

1. 应用启动阶段

应用启动阶段是个线性的过程,比较好理解:

关键时间点

  • Before JS:原生代码加载,约 50-200ms
  • JS Load:加载 bundle,影响最大(可拆包优化)
  • JS Exec:执行 JS 代码,初始化模块
  • First Paint:首帧渲染

2. Hermes 引擎的优化

Hermes 是 Facebook 为 RN 定制的 JS 引擎,要比之前启动速度提升30%以上,因为Hermes将字节码做了预编译:

传统:JS 源码 ➔ 解析 ➔ 编译 ➔ 执行

而Hermes:JS ➔ 预编译为 HBC 字节码 ➔ 直接执行

五、所以,为什么RN快?

优化方向 具体措施 原理
启动速度 启用 Hermes、使用 Codegen、减少 bundle 体积 减少 JS 执行和解析时间
通信效率 迁移到 TurboModules、减少 Bridge 调用 同步调用,避免序列化
渲染性能 使用 Fabric、合理使用 Memo/VirtualizedList 减少线程切换和重绘
内存占用 及时销毁监听器、避免在 state 存大数据 防止内存泄漏

六、总结对比

特性 旧架构 (Bridge) 新架构 (JSI+Fabric)
通信方式 异步 JSON 序列化 同步直接调用
线程模型 多线程切换频繁 C++ 层统一处理
启动速度 较慢 提升 30-50%
内存占用 较高 更低
推荐度 ❌ 已废弃 ✅ 必须使用

实际建议 :新项目强制启用新架构(newArchEnabled=true),旧项目逐步迁移。理解运行时原理是写出高性能 RN 代码的基础。

相关推荐
Jing_Rainbow1 小时前
【前端三剑客-9 /Lesson17(2025-11-01)】CSS 盒子模型详解:从标准盒模型到怪异(IE)盒模型📦
前端·css·前端框架
爱泡脚的鸡腿1 小时前
uni-app D6 实战(小兔鲜)
前端·vue.js
青年优品前端团队1 小时前
🚀 不仅是工具库,更是国内前端开发的“瑞士军刀” —— @qnvip/core
前端
骑自行车的码农1 小时前
🍂 React DOM树的构建原理和算法
javascript·算法·react.js
北极糊的狐2 小时前
Vue3 中父子组件传参是组件通信的核心场景,需遵循「父传子靠 Props,子传父靠自定义事件」的原则,以下是资料总结
前端·javascript·vue.js
看到我请叫我铁锤2 小时前
vue3中THINGJS初始化步骤
前端·javascript·vue.js·3d
q***25212 小时前
SpringMVC 请求参数接收
前端·javascript·算法
q***33372 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
烛阴2 小时前
从`new()`到`.DoSomething()`:一篇讲透C#方法与构造函数的终极指南
前端·c#