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 代码的基础。

相关推荐
少油少盐不要辣12 分钟前
前端如何处理AI模型返回的流数据
前端·javascript·人工智能
IT_陈寒15 分钟前
Java21新特性实战:5个杀手级改进让你的开发效率提升40%
前端·人工智能·后端
跟着珅聪学java15 分钟前
以下是使用JavaScript动态拼接数组内容到HTML的多种方法及示例:
开发语言·前端·javascript
BD_Marathon18 分钟前
NPM_配置的补充说明
前端·npm·node.js
巴拉巴拉~~23 分钟前
KMP 算法通用图表组件:KmpChartWidget 多维度可视化 + PMT 表渲染 + 性能对比
前端·javascript·microsoft
智算菩萨29 分钟前
基于spaCy的英文自然语言处理系统:低频词提取与高级文本分析
前端·javascript·easyui
刘一说39 分钟前
Vue单页应用(SPA)开发全解析:从原理到最佳实践
前端·javascript·vue.js
疯狂成瘾者40 分钟前
前端vue核心知识点
前端·javascript·vue.js
Laravel技术社区2 小时前
用PHP8实现斗地主游戏,实现三带一,三带二,四带二,顺子,王炸功能(第二集)
前端·游戏·php
m0_738120723 小时前
应急响应——知攻善防Web-3靶机详细教程
服务器·前端·网络·安全·web安全·php