React Native 底层架构:它到底是怎么把 JS 变成 App 的?

13 阅读6分钟

React Native 底层架构:它到底是怎么把 JS 变成 App 的?

很多人开始学 React Native 时都有一个疑问:我写的 JavaScript,最后到底是怎么跑在手机上的?是被编译成了原生代码,还是另有玄机?这篇文章就来彻底讲清楚这件事。


一、先回答最核心的问题

RN 不会把 JS 编译成 Swift / Kotlin / Java 原生代码。

JS 代码依然是 JS 代码,它运行在一个内嵌的 JavaScript 引擎里(现在默认是 Hermes,早期是 JavaScriptCore)。

但 —— UI 渲染是真实原生的。你写的 <View><Text><Button> 这些组件,最终对应的是 iOS 的 UIViewUILabelUIButton,以及 Android 的 android.view.View不是 WebView,不是 Canvas 绘制,是货真价实的原生组件

所以 RN 的本质是:用 JS 描述 UI 和逻辑,用一个通信层把指令传递给原生平台去渲染和执行


image.png

二、旧架构:Bridge 模型(< RN 0.68)

三条线程

旧架构下,RN 有三条核心线程:

JS 线程负责执行 JavaScript 代码,包括 React 组件树的构建、setState 触发的重新渲染、业务逻辑、事件处理等。所有你写的 JS 代码,都在这里跑。

**Shadow 线程(Layout 线程)**负责计算布局。RN 使用 Facebook 开源的 Yoga 布局引擎(基于 Flexbox),JS 线程把组件的样式信息传过来,Shadow 线程计算出每个节点的具体位置和尺寸。

**UI 主线程(Native 线程)**负责实际渲染。把计算好的布局应用到真实的原生视图上,更新屏幕显示。

Bridge 是什么

这三条线程之间的通信,全靠 Bridge(桥) 来协调。

Bridge 本质上是一个异步的消息队列,工作方式如下:

  1. JS 线程产生一个操作(比如"创建一个红色的 View,宽 100,高 50")
  2. 把这个操作序列化成 JSON 字符串
  3. 通过队列异步传递给 Native 侧
  4. Native 侧反序列化 JSON,解析出指令
  5. 创建对应的原生组件

反向通信(Native → JS,比如用户点击了按钮)也是同样的流程,只是方向相反。

Bridge 的问题

这套机制在 2015 年 RN 刚发布时已经相当聪明,但随着应用复杂度提升,问题暴露得越来越明显。

性能瓶颈: 每一次 JS 和 Native 之间的通信,都要经历 JSON 序列化和反序列化。数据量大的时候,这个开销不可忽视。更致命的是,这个通信是异步的,所以 JS 无法直接同步读取 Native 的数据(比如页面滚动位置、视图尺寸),只能等回调。

启动慢: 所有的 Native Modules(相机、蓝牙、文件系统等)在 App 启动时全部初始化,不管你用不用。

帧率问题: 在做复杂动画或快速滚动时,JS 线程的任何卡顿都会直接影响 UI 流畅度,因为两侧无法同步。


三、新架构:JSI + Fabric + TurboModules(>= RN 0.68)

新架构于 2022 年前后逐步落地,从根本上解决了 Bridge 的问题。

JSI:JavaScript Interface

JSI 是新架构的核心,它是一个用 C++ 编写的轻量层,让 JavaScript 引擎可以直接持有对原生对象的引用,并且同步调用原生方法。

这是质的变化。在旧架构里,JS 要调用相机,得序列化一条 JSON 消息扔给 Bridge,等 Native 处理完再回调给你。在新架构里,JS 可以直接拿到一个代表相机模块的 C++ 对象引用,同步调用它的方法,立刻拿到返回值

Fabric:新的渲染引擎

Fabric 是对旧渲染流水线的全面重写,基于 JSI 构建。

最大的改变是:渲染流程可以同步执行。旧架构里,布局计算(Shadow 线程)和渲染(UI 线程)之间需要等待 Bridge 传递,而 Fabric 让这两个步骤可以在同一帧内完成,同时支持 React 18 的并发模式(Concurrent Mode) ,渲染任务可以被中断和优先级调度。

TurboModules:按需懒加载

TurboModules 是对原来 Native Modules 的升级。在旧架构里,所有模块启动时全部初始化。TurboModules 改为懒加载,只有你第一次调用某个模块时,它才被初始化。大幅减少 App 启动时间。

Codegen:编译期的类型安全

新架构引入了 Codegen,在编译阶段根据你写的 TypeScript/Flow 类型定义,自动生成 JS 和 Native 之间的接口代码,保证类型一致,减少运行时错误。


四、静态 UI vs 动态逻辑,官方说的到底是什么意思

你提到官网说"静态页面会转为原生代码,涉及逻辑的通过桥梁处理",这个描述其实说的是渲染层 vs 逻辑层的分工,并不是说 JS 被编译成了原生代码。

更准确的理解是:

UI 部分(View 层): 你写的 <View style={{backgroundColor: 'red'}} /> 这样的声明,最终会在原生层创建一个真实的原生 View。渲染结果是原生的,但驱动这个渲染的指令来自 JS。

逻辑部分(Business Logic): 所有的条件判断、网络请求、状态管理、事件响应,这些始终运行在 JS 引擎里。每当状态变化触发界面更新,就通过通信层(Bridge 或 JSI)告诉原生层"把这个 View 的颜色改成蓝色"。

所以本质上没有任何 JS 被"编译"成原生代码,而是 JS 始终作为指挥者,原生层作为执行者。


五、和 Flutter 的一句话对比

顺手提一下,因为经常有人问:

React Native 的原生组件是调用平台 UI 组件,所以 iOS 上的按钮长得像 iOS 按钮,Android 上长得像 Android 按钮,跟平台风格一致。

Flutter 的渲染完全绕过平台 UI 组件,用自己的 Skia/Impeller 引擎从像素级别绘制所有界面,跨平台像素级一致,但和平台原生组件没有继承关系。

两种思路各有取舍,不存在谁更好,只是架构选择不同。


六、总结

问题答案
JS 会被编译成原生代码吗?不会,JS 始终运行在 JS 引擎(Hermes)中
UI 是原生的吗?是,最终渲染的是平台原生组件
旧架构通信方式?Bridge,异步 + JSON 序列化
新架构通信方式?JSI,直接 C++ 引用,可同步调用
新架构渲染引擎?Fabric,支持并发渲染和优先级调度
新架构模块加载?TurboModules,懒加载,加快启动

React Native 的本质是一套精心设计的跨语言运行时桥接方案。它不是语言转换器,而是让 JS 世界和 Native 世界各司其职、协同工作。新架构的 JSI 打破了异步通信的瓶颈,让这个协作变得更加高效和紧密。


参考:React Native 官方新架构文档

此篇文章来源来claudeAI编写