React Native 技术原理:从 TypeScript 到原生界面、Fabric 与 Hermes

This article is extracted from the chat log with AI. Please identify it with caution.

AI 参与说明(Agent:Codex):本文由 Codex 根据 React Native、Hermes、Metro、Apple 与 Android 的官方公开文档协助整理,重点核对新架构、构建管线、测试、发布与原生 API 适配边界。资料初次核验于 2026-08-10,并于 2026-08-12 复核原生 API 的维护责任;版本、构建工具和商店规则会变化,请以文末官方链接和项目实际版本为准。所有标识、密钥与账号均为占位示例。

适用范围:本文面向已经会 React / TypeScript、希望理解 React Native 运行原理的开发者。它以 React Native 0.86 为资料快照;Expo 如何组织 React Native 项目、CNG 与 EAS 如何交付,请阅读 Expo 技术原理与交付

先说结论#

React Native 是用 React 描述界面、再把宿主组件渲染为 iOS / Android 平台视图的框架。它不是把网页放进 WebView,也不会把普通 .tsx 业务源码逐行翻译成 Swift、Kotlin 或 ARM 机器码。

更准确的模型是:

TypeScript / JSX
  → Babel / Metro 转换与打包
  → Hermes 执行 JavaScript(release 通常使用 Hermes bytecode)
  → React + Fabric 形成和提交 Shadow Tree
  → Yoga 计算布局
  → UI thread 挂载 / 更新 iOS、Android Host Views

与此同时,原生侧是另一条独立编译链:Swift / Objective-C / C++ 由 Xcode 编译,Kotlin / Java / C++ 由 Gradle / NDK 编译。最后,JavaScript bundle / bytecode、原生库、图片字体和签名材料一起组成 iOS 的 .xcarchive / 可导出的 .ipa,以及 Android 的 .aab.apk共享的是多数 React / TypeScript 源码,不是一份跨平台二进制。

截至本文整理日,React Native 0.86 是官方 stable 线;0.82 起运行时只使用 New Architecture,0.84 起 Hermes V1 默认启用。不要跟随更早教程去关闭 newArchEnabled;Hermes 是默认且与 React Native 版本配套的 engine,无需另装,仍可按官方指引切换 JavaScriptCore。React Native versions React Native 0.82 React Native 0.84 Using Hermes

1. 先建立概念地图#

概念它是什么不是什么
React Native用 React 构建原生应用的框架WebView 容器、两套原生业务代码生成器
MetroReact Native 的 JS / 资源 bundlerSwift / Kotlin 编译器、模拟器
Babel把 JSX、现代 JS / TS 语法转换为可执行 JS 的转换器TypeScript 类型检查器的替代品
Hermes针对 React Native 优化的 JavaScript enginebundler、UI renderer、通用 ARM 编译器
FabricNew Architecture 的 rendererJavaScript engine、导航库
Shadow TreeFabric 用 C++ 表示的不可变渲染树DOM、最终原生 View 树
Yoga可移植的 Flexbox 风格布局 engine浏览器完整 CSS、Auto Layout / ConstraintLayout
JSIJS engine 与 C++ / 原生边界交互的接口“零成本、所有调用同步”的保证
TurboModuleNew Architecture 下的非 UI 原生能力模块任何页面组件、自动生成的原生实现
Codegen从受约束的 TS / Flow spec 生成跨边界 glue / 接口把所有 TS 业务逻辑变成原生代码
Fabric Native Component将原生 View / 控件暴露给 React 的方案普通函数组件的必经步骤

如果记不住全部名词,先记三个职责:Metro 打包,Hermes 跑 JS,Fabric 显示 UI。

2. 两条构建管线:开发与 Release#

开发态#

开发时,Metro 运行在本机,解析依赖、通过 Babel 转换 JSX / TS 语法、生成 bundle 并把它提供给 Debug 原生壳;设备上的 Hermes 执行代码,Fast Refresh 尽可能保留界面状态并重载已修改模块。

App.tsx / *.ts
  → Babel 转换(类型不会进入运行时)
  → Metro:Resolution → Transformation → Serialization
  → Debug App 通常通过 Metro URL 读取 bundle / assets
  → Hermes 执行 → Fabric 更新 Host Views

Metro 还会按平台解析文件,例如 Button.ios.tsxButton.android.tsxButton.native.tsx,并处理图片等 assets。它不负责签名或编译 Swift / Kotlin。注意,Debug 真机的默认 Xcode 脚本也可能生成一个 dev bundle;但典型 Debug App 仍使用 Metro URL,不能把它当作 Release 内置 bundle 的验证。Metro concepts Metro resolution

Release / 上架态#

Release 构建将 JS bundle 与 assets 内置进 App。当前 Hermes 配置下,构建会将 JavaScript 预编译为 Hermes bytecode;这个产物仍由 JavaScript engine 执行,不应称为“把业务代码 AOT 成 iOS / Android CPU 机器码”。

flowchart TB
  subgraph JS["JavaScript 链"]
    A["TypeScript / JSX"] --> B["Babel"]
    B --> C["Metro bundle + assets"]
    C --> D["Hermes bytecode(Release)"]
  end

  subgraph Native["原生链"]
    E["Swift / ObjC / ObjC++ / C++"] --> F["Xcode"]
    G["Kotlin / Java / C++"] --> H["Gradle / NDK"]
  end

  D --> I["签名 iOS archive / .ipa"]
  F --> I
  D --> J["签名 Android .aab / .apk"]
  H --> J

TypeScript 在普通 bundle 中主要提供类型安全:Babel 会移除类型,tsc --noEmit 应独立用于类型检查。例外是 Codegen spec:构建工具会读取它受约束的 TypeScript / Flow 类型,生成 C++ glue 及 Java / Objective-C++ scaffolding / interfaces;这并不意味着任意业务 TS 会生成 Swift / Kotlin 实现。React Native TypeScript React Native Gradle Plugin

iOS:Release 配置究竟多做了什么#

如果已经熟悉 Swift / Objective-C App 的 Archive,React Native 多出来的并不是另一套“上架流程”,而是一条在 Xcode 构建期间生成 App 资源的 JavaScript 构建链

“用 Release 配置构建,确保 JS bundle 被内置”更准确的意思是:确认 Archive 所用 configuration 中的 Bundle React Native code and images Run Script Build Phase 已执行,并在最终 .app 的 resources 中生成可加载的 bundle。 Community CLI / bare 模板会为所有 configuration 执行这个 phase;脚本内部才根据 $CONFIGURATION 决定 DEV。当 configuration 名称不匹配 *Debug* 时,它使用 DEV=false 生成和校验生产启动路径所需的本地 bundle。标准项目通常让 Archive 使用 Release,但 Xcode Scheme 的 RunArchive 是不同 action;把 Run 改成 Release 适合本地 smoke test,不自动保证 Archive action 也使用同一 configuration。应在 Product → Scheme → Edit Scheme → Archive 中核对实际配置。

这条资源链与原生编译链的关系如下:

index.js(或 ENTRY_FILE)
  → Metro 读取依赖图、转换 JS、收集静态 require() assets
  → 中间 bundle
  → Hermes(默认启用时)编译为 bytecode
  → <App>.app/main.jsbundle + 资源文件
  → Xcode 对最终 .app 签名
  → .xcarchive / .ipa

因此,“内置”不是把 .tsx 编译成 Swift 或 ARM 指令,而是把 React Native 运行时将在启动时加载的 bundle 作为已签名 App 的资源放进去。网络图片、API 响应和运行时下载的内容不会因此自动进入 IPA;Metro 只会处理它在依赖图中可静态识别的资源。

Xcode 的那一个 Build Phase 实际做什么#

以下以 React Native Community CLI / bare 模板为例:承载 React Native root 的 App target 默认有一个名为 Bundle React Native code and images 的 Run Script Build Phase。把 React Native 嵌入既有原生 App 时,需要为该 target 配置等效的生成与嵌入步骤;Expo / CNG / EAS 的构建产物管理则应遵循其自己的工作流,不应照抄本节的 Xcode phase。当前 0.86 脚本链可以简化为:

阶段运行者作用
环境准备with-environment.sh读取 ios/.xcode.env,再读取本机覆盖文件 .xcode.env.local,确定 NODE_BINARY 后执行下一段脚本
配置判断react-native-xcode.sh根据 Xcode 注入的 $CONFIGURATION 决定 DEV=trueDEV=false
Metro 打包Node + MetroENTRY_FILE,或存在时优先从 index.ios.js,否则从 index.js 生成 iOS bundle,并把可追踪的静态 assets 写到 App resources 目录
Hermes 编译hermesc,默认启用把中间 JavaScript bundle 编译为 Hermes bytecode,写入最终 resource bundle
原生收尾Xcode原生代码、Pods、资源和最终 main.jsbundle 一起进入已签名 .app,随后才形成 archive

用当前脚本的关键路径来理解即可,不应手抄整段脚本到项目中:

with-environment.sh
  → react-native-xcode.sh
  → Metro: --platform ios --dev false
       --bundle-output <intermediate>/main.jsbundle
       --assets-dest <App>.app
  → hermesc -emit-binary -O
       -out <App>.app/main.jsbundle

默认输出名是 main.jsbundleBUNDLE_NAME 可以改变输出名,ENTRY_FILE 改变打包入口,而 AppDelegate / 自定义启动器决定运行时查找哪个 bundle。排查时应以项目的 Build Phase 与启动代码为准,而不是把名称当作绝对约定。当前 React Native iOS bundling script(0.86.2) 环境脚本(0.86.2)

main.jsbundle 是文本 JS,还是 Hermes bytecode?#

取决于是否使用 Hermes:

配置Metro 先产出什么最终进入 <App>.app 的默认 main.jsbundle
USE_HERMES=falseJavaScript bundle同一份 JavaScript bundle 被复制进 resources
默认 Hermes Release中间 JavaScript bundlehermesc -emit-binary -O 生成的 Hermes bytecode,扩展名仍是 .jsbundle

这解释了两个常见误解:

  • 不要因为文件名是 .jsbundle 就断言它是可读、压缩过的文本 JavaScript;默认 Hermes Release 的最终文件是 bytecode。
  • 也不要把“Release 的 Metro bundle”简单等同于“Metro minify”。当前 React Native 脚本在 Hermes Release 中把 --minify false 传给 Metro,再由 Hermes 以 -O 编译和优化。它仍是 JavaScript engine 可执行的 bytecode,不是 iOS CPU 的通用原生机器码。

Hermes 的好处之一是 release 中的 bytecode 可按需加载,不需要像纯文本 JavaScript 一样先解析;这属于启动与内存优化,不是保密机制。任何进入客户端 binary 的业务逻辑、常量和令牌都不应被当作服务端秘密。Optimizing JavaScript loading

Debug、Release 与 Archive 应如何区分#

构建情形默认 bundling 行为应把它理解成什么
Debug Simulator脚本默认跳过 bundling,由 packager / Metro 提供代码开发体验,不验证离线的上架资源路径
Debug 真机默认脚本会生成 dev bundle,除非设置 SKIP_BUNDLING;典型启动代码优先连接 Metro,不可达时可退回本地 dev bundle方便真机开发,仍不是最终上架路径
Run action 使用 ReleaseDEV=false,生成本地 bundle,可做离线 release smoke验证接近生产的本机 App,但不是 Archive 的替代物
Archive action 使用 Release(常见默认)同样执行资源生成并把最终 bundle 放进 archive 的 .app真正需要检查和分发的产物

React Native 官方发布文档也明确说明:Release 会关闭 Dev Menu 并在本地 bundle JavaScript;物理设备 Debug 的静态 bundle 行为是脚本层的优化细节,两者不能混为一谈。Publishing to Apple App Store

DEV=false 会让 Metro 以非开发模式构建,应用代码中的 __DEV__false;它不是“所有优化已经自动完成”的同义词。尤其在默认 Hermes Release 中,Metro 的 --minify falsehermesc -O 是有意配合的两个步骤。

自定义 StagingQA 之类的 configuration 时,要同时看两套判断:RN 脚本按 configuration 名称是否包含 Debug 决定 DEV,Swift / Objective-C 启动代码又可能按 DEBUG 编译条件决定取 Metro URL 还是本地 bundle。两者设计不一致时,可能得到“DEV=false 却仍去找 Metro”或相反的组合;不要只给 Scheme 换一个漂亮的名字就假定行为正确。

在原生启动侧,典型模板的意思可概念化为:

// 概念示意:具体 AppDelegate API 会随 RN 模板和 ObjC / Swift 写法不同。
#if DEBUG
return developmentServerURL(for: "index")
#else
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif

也就是说,Release 启动时不是向你的 Mac 上的 Metro 请求 index.bundle,而是从 Bundle.main 读取那个构建阶段生成的资源。若项目自定义了 bundle URL、bundle 名或启动器,这三处必须保持一致。

如何验证,避免“Archive 成功但 JS 没进去”#

不要只看 Xcode 显示 Archive succeeded。建议对同一个 archive 做四个检查:

  1. 在 Xcode Build Report 搜索 Bundle React Native code and imagesWriting bundle outputhermesc;Hermes Release 还应看到 --dev false-emit-binary -O,并确认没有 SKIP_BUNDLING enabled; skipping.

  2. 右键 .xcarchive 选择 Show Package Contents,查看 App resources 中的 bundle(Community template 默认是 Products/Applications/<App>.app/main.jsbundle)。若修改过 BUNDLE_NAME,用实际输出名检查;也可以在终端列出 bundle:

    APP='<archive>.xcarchive/Products/Applications/<App>.app'
    find "$APP" -maxdepth 1 -type f -name '*.jsbundle' -print
  3. 导出 IPA 后可再次验证最终分发物,而不是只看 DerivedData:

    unzip -l '<App>.ipa' | grep -E 'Payload/[^/]+\.app/[^/]+\.jsbundle'
  4. 安装同一个 archive / TestFlight 构建,关闭 Metro、让设备离线、杀掉进程后重新启动。React Native 根界面仍能启动,才说明运行时没有依赖开发服务器。

对标准 RN 0.86 Community Template,不要手工把 ios/main.jsbundle 提交到 Git 来“修复”问题;它是按本次 source、Metro 配置、Hermes 版本和 build configuration 生成的构建产物。手工复制很容易让 Archive 带入过期 bundle,或让 bytecode 与 App 内 Hermes runtime 不匹配。Brownfield 项目若有自定义加载链,也应只保留一条明确、可复现的生成与嵌入流程。

source map 是另一个产物,不应误认为已随 IPA 内置#

iOS 的 source map 默认关闭。若需要把线上 Hermes / 压缩栈还原到源文件和行号,应在同一个 Build Phase 中、调用 RN 脚本前设置 SOURCEMAP_FILE,并把输出作为 CI artifact 上传到错误监控平台;示意如下:

# 输出路径仅为示意;CI 应保存为受控的构建产物。
export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"

默认 Hermes 流程会分别生成 Metro map 和 Hermes map,再合成为最终 source map;临时 map 会被清理。最终 map 必须与精确的 commit、bundle、Hermes 版本和 archive配对。它通常不应进入最终 IPA:一方面客户端不需要它,另一方面 map 会暴露可读的源码结构,应以受控的构建产物保存。Debugging Release Builds

不要把 JS source map 和 iOS 原生 dSYM 混为一谈:

用途产物映射什么App 运行时是否需要
原生崩溃符号化dSYMSwift / Objective-C / C++ / Framework 地址 → 原生源码
React Native JS 异常符号化main.jsbundle.mapHermes bytecode offset → TS / JS 源文件、行列
App 启动和业务执行main.jsbundle文本 JS 或 Hermes bytecode

几个真正影响 CI / 线上排障的失败模式#

现象常见根因先检查什么
Archive 成功,设备启动却提示没有 bundle URL / 根界面无法加载Build Phase 被删除、SKIP_BUNDLING 被继承,或 AppDelegate 指向了错误名称 / URLarchive 内是否有 bundle;Build Report;Release bundle URL
本机能 Archive,CI 报 node 找不到Xcode / CI 是非交互 shell,Node 路径没有进入构建环境ios/.xcode.envNODE_BINARY 与 CI Node 安装位置
Release 线上 JS 堆栈只有压缩名或 bytecode offsetiOS map 默认未生成,或上传的 map 不对应该构建SOURCEMAP_FILE、CI artifact 与 release build ID
“发布后还是旧 JS”上传 / 安装了错误 archive、Build Phase 被跳过,或自定义 CI / OTA 缓存策略错误对照 bundle 生成日志、构建号、IPA 内 bundle 与实际分发渠道
为了改一行 JS 而重新 Archive对纯裸 React Native,这是正常的签名发布路径若需要 OTA,应另行评估合规的更新机制;它不是内置 bundle 的一部分

最后一项尤其重要:没有额外 OTA 机制时,任何 JavaScript 改动都会随着新的 archive / IPA 发布;Release 内置 bundle 是“二进制自包含”的定义,不是绕过商店更新的渠道。

3. 最小运行示例:哪些节点会变成原生视图#

先按官方 Community CLI 创建一个没有框架层的项目:

npx @react-native-community/cli@latest init AwesomeProject --version 0.86
cd AwesomeProject
npm start

这条命令固定的是本文的版本快照;仍须先按 React Native 环境配置 安装 Node、JDK、Android Studio,以及 iOS 所需的 macOS / Xcode。

另开一个终端运行 Android,或在 macOS 上运行 iOS:

npm run android
npm run ios

App.tsx 改为:

import { useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <View style={styles.screen}>
      <Text accessibilityRole="header">Count: {count}</Text>
      <Pressable
        accessibilityRole="button"
        onPress={() => setCount((value) => value + 1)}
        style={styles.button}
      >
        <Text>Increment</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, alignItems: "center", justifyContent: "center", gap: 12 },
  button: { borderRadius: 8, backgroundColor: "#dbeafe", padding: 12 },
});

预期结果是一个可点击计数器。这里的 Countercomposite component:它本身不会获得一个对应的原生 View。React 会继续归约 CounterPressableViewText 等 Host Component 才创建 Shadow Node 并进入 Fabric 的原生渲染链。Pressable 是交互抽象,最终由 View 等宿主组件承载,不应理解为额外且稳定的一对一原生视图。Pressable

4. Hermes:JavaScript 到底在哪里运行#

Hermes 是 React Native 默认使用、并与 React Native 版本绑定测试的 JavaScript engine。官方说明指出,许多应用能借此改善启动时间、内存使用和包体,但效果必须在真实 release 构建上测量,不能把“使用 Hermes”当作性能保证。Using Hermes Bundled Hermes

它在这里负责:

  1. 在设备上执行 React、业务逻辑、定时器与大部分 JS 事件处理;
  2. 在 Release 构建中加载预编译的 Hermes bytecode,减少设备启动时的解析工作;
  3. 为 JSI 提供可嵌入、可与 C++ 交互的 JavaScript runtime。

负责:解析 npm 依赖(Metro 负责)、绘制原生 UI(Fabric / 平台负责)、把每个 JS 函数改写成 Swift / Kotlin、替代 Xcode / Gradle。

可用下列代码确认当前 runtime 是否暴露 Hermes 标记,但不要只凭它判断 bytecode 是否按最佳方式加载;性能判断仍应跑 release benchmark:

export const isHermes = () => Boolean(global.HermesInternal);

开发版和生产版行为不同。官方建议用 release mode 对比 Hermes:

npm run android -- --mode="release"
npm run ios -- --mode="Release"

5. Fabric:React 怎样显示为 iOS / Android 界面#

Fabric 的核心渲染流程是 Render → Commit → Mount。它解释了为什么 React Native 既有 React 的声明式更新,又能更新平台视图。

flowchart LR
  A["Hermes 执行 React"] --> B["React Element Tree\nJavaScript"]
  B --> C["React Shadow Tree\nC++ / Fabric"]
  C --> D["Commit\nYoga 布局 + tree promotion"]
  D --> E["C++ diff + View Flattening"]
  E --> F["原子 mutation\ncreate / update / remove"]
  F --> G["UI thread Mount"]
  G --> H["iOS / Android Host Views\n由 OS 绘制"]

Render:从 React Element 到 Shadow Node#

React 在 JS 中运行函数组件并形成 Element Tree。Fabric 会为 Host Component 创建 C++ Shadow Node;函数组件、业务组件、Hooks 不是 Host View,因此不会一一对应 Shadow Node。Shadow Tree 保存 Host Component 的类型、props、children 与布局信息;普通 React / Hooks state 仍由 React 管理,只有少数宿主组件另有 C++ State。它以不可变结构支持线程安全更新。Render, Commit, and Mount

Commit:Yoga 计算布局#

Commit 阶段把新 Shadow Tree 作为待挂载树,并让 Yoga 计算大多数节点的尺寸和位置。Yoga 是可移植 C++ 布局 engine,使用 Flexbox 风格规则;它既不是浏览器 CSS 的完整实现,也不是 iOS Auto Layout 或 Android ConstraintLayout。

浏览器 CSS 与 React Native Flexbox 的默认值有差异,例如 React Native 的 flexDirection 默认 columnflexShrink 默认 0。把 Web 样式照搬到移动端前应先核对这些差异。React Native Flexbox Yoga

文本、文本输入等尺寸有平台相关部分,Yoga 可以请求宿主平台测量;因此不要承诺所有布局永远只在 C++ 内完成。

Mount:在 UI thread 修改 Host Views#

Fabric 在 C++ 中对已渲染树和下一棵树做 diff,产生 createViewupdateViewremoveViewdeleteView 等原子 mutation;平台 UI thread 再把它们应用到 Host View 上。Android 可对应 ViewGroupTextView 等,iOS 由 UIView 和平台文本系统承载。具体实现可能是 React Native 专用子类或组合,不应粗暴等同为“每个 JSX 都是一个 stock UIKit / Material 控件”。

View Flattening 会去掉或合并某些只用于布局的节点,减少原生 View 数量。因此“1 个 React 组件 = 1 个原生 View”有两层错误:函数组件本来不创建 Host View;布局节点也可能被 flatten。View Flattening

6. New Architecture:JSI、TurboModules 与 Codegen#

New Architecture 是一组协作机制,而不是一个“性能开关”。关键组成包括新 renderer(Fabric)、新原生模块系统(TurboModules)、JSI、Codegen 与新的 event loop 工作。它为同步布局访问、并发 React 特性和更直接的 JavaScript / 原生交互创造条件;官方也明确指出,启用新架构不会自动让每个 App 更快。New Architecture is here React Native 0.82

JSI:减少旧 Bridge 的固定序列化模型#

旧 Bridge 以异步、批量、序列化消息为主要模型。JSI 是轻量 C++ 接口,使 JS engine 可以直接持有 / 交互 C++ 对象引用,移除这一固定 JSON 序列化桥的依赖。

这不等于“没有成本”:对象转换、线程调度、平台 API、I/O、内存复制和错误处理仍可能昂贵;同步 API 也不应该拿来阻塞 JavaScript 或 UI 线程。更准确的说法是:JSI 改善跨边界的能力与模型,但性能要按具体调用路径测量。 React Native glossary

TurboModules:给非 UI 原生能力建立类型契约#

TurboModule 适合存储、设备 API、音频、数据库等非 UI 原生能力。它可延迟加载,并通过受约束的类型契约与 Codegen 接入。下面是一个缩短后的 spec;它本身还不能工作,因为 Android / iOS 实现尚未编写:

// specs/NativeLocalStorage.ts
import type { TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";

export interface Spec extends TurboModule {
  setItem(value: string, key: string): void;
  getItem(key: string): string | null;
  removeItem(key: string): void;
  clear(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>("NativeLocalStorage");

配套包配置让 Codegen 知道在哪里找 spec:

{
  "codegenConfig": {
    "name": "NativeLocalStorageSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.nativelocalstorage"
    },
    "ios": {
      "modulesProvider": {
        "NativeLocalStorage": "RCTNativeLocalStorage"
      }
    }
  }
}

Codegen 随 iOS / Android 构建生成接口与 glue;开发者仍须分别实现 Android 端,以及 iOS 或共享 C++ 端的业务能力,并在两端真机测试。本例的 iOS provider 使用 Objective-C++;若主体逻辑写在 Swift 中,仍需 Objective-C++ adapter。新增或修改 spec / codegenConfig 后,应在 ios/ 目录运行 bundle exec pod install,使 Codegen 重新生成并接入产物。完整教程见 Turbo Native Modules TurboModules with Swift

Fabric Native Component:当你要包一个原生 View#

若要把一个平台 UI 控件、原生地图 / 视频 / 相机预览 Surface 暴露给 React,则使用 Fabric Native Component。它与 TurboModule 的区别是:前者要参与 UI 树和 props / events,后者主要提供非 UI API。先评估已有成熟库,只有组件本身是产品差异化或 SDK 无合适封装时才自建。Fabric Native Components

谁负责新 Apple API 的适配?#

不需要把 Fabric、TurboModules 或 JSI 理解为“随每个 Apple API 自动更新的翻译层”。它们分别负责原生 View 的接入 / 渲染、非 UI 原生能力的类型契约,以及 JavaScript runtime 与 C++ / 原生层的互操作;Apple 新增 ARKit、相机或 StoreKit API 时,真正需要变化的是使用该能力的原生封装。已有库由库维护者加入或升级 iOS 实现;没有合适库时,应用在 Swift / Objective-C(++) 中实现,并按产品需要提供 Android 对应实现或降级路径。

Codegen 只会在 spec 变化后生成跨边界的接口和 glue,不会生成 ARKit 等业务实现;JSI 也不是 Apple API 的映射表。只有新增 JS props / methods / events、升级 React Native 或原生依赖,或 Xcode、iOS SDK、New Architecture 的兼容性发生变化时,相关 wrapper 才需要改动、重新构建并做真机回归。新 API 也不等于必须提高全体用户的最低系统版本:原生封装可用 if #available(...) / @available 做版本判断并保留旧系统 fallback。权限用途文案、隐私清单、SDK 签名和商店规则的变化,则是独立于 Fabric / JSI 的持续维护事项。Turbo Native Modules What is Codegen? Fabric Native Components Apple API availability

兼容与旧库#

从 0.82 起不能切回 Legacy Architecture;但 React Native 仍有 Interop Layer 帮助部分旧库逐步过渡。不要把“Bridge 移除”写成“所有旧代码和所有兼容层已经不存在”,也不要因为 New Architecture 就跳过依赖兼容性测试。New Architecture is here React Native 0.84

7. 调试、性能与测试#

调试分工#

问题优先工具原因
React state、JS exception、breakpoint、JS heap / 对象生命周期、组件树React Native DevTools它连接设备内 Hermes runtime,适合 JS / React 层
原生崩溃、Swift / Kotlin、JNI、原生 UI、CPU / GPUXcode Instruments、Android Studio Profiler / System Trace需要看平台线程、符号和原生调用栈
Release JS stack与该构建严格匹配的 source map、metro-symbolicate生产 bundle 已转换 / 压缩,不能拿任意本地源码猜测

早期“在 Chrome 中远程运行 JavaScript”的资料已经过时;Remote JavaScript Debugging 自 React Native 0.79 起已移除。React Native DevTools 和原生 IDE 应各司其职;native heap / allocation 则继续使用 Instruments 或 Android Studio Profiler 查看。React Native DevTools Other debugging methods Debugging native code Debugging release builds

性能:先定位在哪一层掉帧#

60 Hz 设备每帧约有 16.67 ms;120 Hz 设备预算更小。不要笼统说“React Native 卡”,先区分:

  • JS / React 层:昂贵计算、过大 re-render、同步循环、日志和状态放置不当;
  • UI / main thread:频繁视图 mutation、复杂测量、导航动画、触摸响应;
  • GPU / 图形:大图、透明叠加、阴影、频繁缩放 / 重绘;
  • 列表 / 资源:过多 item、无稳定 key、图片解码、网络和缓存;
  • 启动 / 内存:初始 bundle、同步初始化、第三方 SDK、长期持有大对象。

应在 release / profile 构建、代表性真机、低端 Android 和目标 iPhone 上测量。开发模式的警告与调试负担会显著改变 JS 性能,不能以它作为生产结论。长列表优先 FlatList,固定高度项目可评估 getItemLayout,并在真实数据下权衡填充率、响应和内存。React Native performance Optimizing FlatList

测试金字塔#

  1. tsc --noEmit、lint:类型与静态问题;
  2. Jest:纯函数、领域逻辑、错误模型;
  3. React Native Testing Library:从用户可见文本、role、交互验证组件;
  4. 原生模块双端集成测试;
  5. 模拟器 / 真机 E2E:登录、支付、深链、推送、离线等关键旅程;
  6. release smoke:最终签名二进制、商店测试轨道与监控。

React Native 官方已提示 React Test Renderer 已废弃;Node 中的组件测试不执行 iOS / Android backing code,不能取代 E2E 与真机验证。E2E 可选 Detox、Appium、Maestro 等生态工具,不应写成 React Native 内置能力。Testing overview

React Native 0.85 起,Jest preset 已从 react-native 移到 @react-native/jest-preset。0.86 项目可使用:

module.exports = { preset: "@react-native/jest-preset" };

React Native 0.85

8. 不使用 Expo:Bare React Native 如何打包、签名与上架#

不使用 Expo / EAS 时,谁负责构建、签名与上架?#

这里的 bare React Native 指由 Community CLI 创建、或直接维护 ios/android/ 工程的项目。它不是“没有原生工程”,而是没有 Expo 的框架层;开发团队自己用 Xcode、Gradle 和 CI 接管交付。若你已经上架过 Swift iOS App,iOS 商店流程仍是 签名 → Archive → 上传 App Store Connect → TestFlight → App Review;React Native 新增的关键步骤,只是在原生构建期间生成并嵌入 JavaScript 运行产物。

bare React Native 与不使用 EAS 是两件独立的事。 不用 Expo 时,可以完全不用 EAS、采用下面的本地 / 自建 CI 流程;也可以让 bare RN 项目使用 EAS Build。反过来,Expo 项目也可以使用本地原生构建。EAS 是可选交付服务,不是 Expo runtime 本身。下面的“传统路径”特指既不采用 Expo,也不采用 EAS的团队工作流。EAS Build

交付层iOS(标准 Community CLI 项目)Android(标准 Community CLI 项目)
JS / 资源Xcode target 的 Bundle React Native code and images Build Phase 调用 Metro;默认 Hermes 的 release build 会在构建期生成 bytecode,并把最终 bundle / 静态资源放进 .appReact Native Gradle Plugin 为非 debuggable variant 创建 createBundle<Variant>JsAndAssets task,调用 Metro、hermesc 与 source-map 合成,再把 bundle / assets 放进 AAB
原生编译与签名Xcode 编译 Swift / Objective-C / C++、Pods 与原生库;用 distribution certificate、provisioning profile 签名 archive / 导出 IPAGradle / NDK 编译 Kotlin / Java / C++ 与原生库;release signingConfig 使用 upload keystore 签名 AAB
测试与提交关闭 Metro 做 release smoke;Product → Archive,上传 App Store Connect,处理完成后进入 TestFlight,补齐资料并提交 Review安装 release build 做离线验证;上传 app-release.aab 至 Play Console 的 Internal / Closed / Production track,完成 Play App Signing、商店资料与 rollout
TypeScript / JSX ──Metro──> JS bundle ──Hermes(默认 Release)──> bytecode + assets
原生 Swift / ObjC / C++ ──Xcode──────────────────────────────────> signed .xcarchive / .ipa
原生 Kotlin / Java / C++ ──Gradle / NDK──────────────────────────> signed .aab
                                      App Store Connect / Play Console → 测试、审核、发布

因此,Metro 打包,Hermes 执行或预编译 JavaScript;Xcode 和 Gradle 编译原生代码并产出受签名保护的二进制;商店控制台负责处理、测试轨道、审核与发布。 普通 .tsx 业务代码不会被翻译成 Swift / Kotlin 或 CPU 机器码;Hermes bytecode 仍由 App 内的 JavaScript engine 执行。Publishing to Apple App Store React Native Gradle Plugin Using Hermes Android App Signing

上面的总览解释“谁负责什么”;下面把 Android 和 iOS 的具体产物与提交动作拆开。第 2 节的 iOS Build Phase、main.jsbundle、Hermes bytecode 和 source map 说明,正是这个传统路径中最容易被忽略的 React Native 特有部分。

Android#

  1. 确定 application ID、versionCode / versionName,并生成 / 保管 upload keystore;
  2. 在 Gradle 配置 release signing;
  3. 本地 release smoke 可用 npm run android -- --mode=release 安装 release APK;
  4. 发布 App Bundle 可运行 npx react-native build-android --mode=release,或在 android/ 下运行 ./gradlew bundleRelease
  5. bundletool 做本地安装与设备适配验证;再将 .aab 上传到 Play Console 的 Internal / Closed 等轨道,验证 Play App Signing、商店处理与分发链路,随后完成 Data safety、内容分级、商店资料与正式发布设置。

Google Play 新应用以 .aab 发布;其他商店或本地分发可使用 .apk,以当时规则为准。Play App Signing 负责面向用户的分发签名;upload key 与应用分发 key 的角色要分开理解。React Native 只帮你生成 App,不替代 Play 开发者账号、身份验证、测试门槛与审核。Signed APK / App Bundle Android App Signing

iOS#

  1. 在 macOS / Xcode 配置 Bundle ID、Apple Team、签名、权限用途文案与 Release scheme;
  2. 核对 Archive action 使用的构建配置:
    • 在 Build Report 中确认 Bundle React Native code and images 已执行;
    • 对于非 Debug 配置,检查归档 .app 是否含有可加载的 JS bundle。原理和验证方法见上文“iOS:Release 配置究竟多做了什么”。
  3. 选择 Xcode 的 Product → Archive,再 Validate / Distribute / Upload;
  4. 在 App Store Connect 等待处理,使用 TestFlight 测试;
  5. 补齐 metadata、截图、隐私信息、审核账号和 Review Notes,再提交 App Review。

iOS 原生构建仍需 macOS / Xcode。免费 Apple Account 可用于有限的本机设备开发;TestFlight 与 App Store 分发需要 Apple Developer Program。账号、签名、Archive / TestFlight / App Review 流程与 Swift App 相同;React Native 还需确保 JS / Hermes bundle、Pods / 原生依赖与 Codegen 产物进入 archive,并另行生成、保留和上传与该构建严格匹配的 source map。Publishing to Apple App Store Debugging release builds Apple Developer Program

9. 常见误解核对表#

误解更准确的说法
RN 等于 WebViewFabric 渲染平台 Host Views,不依赖浏览器 DOM
TS 会变成 Swift / Kotlin / ARM普通 TS 经 Babel / Metro 进入 JS bundle;Release 常为 Hermes bytecode
Hermes 是 UI rendererHermes 跑 JS;Fabric 渲染 UI;Metro 打包
JSI 没有任何开销它改变跨边界模型,但仍有转换、线程、I/O 与平台成本
Codegen 自动写好原生功能Codegen 生成契约 / glue,原生实现仍需开发和测试
一个 JSX 一定对应一个 native Viewcomposite component 没有 Host View,layout-only 节点还可能 flatten
Yoga 就是完整 CSSYoga 是有限的 Flexbox 风格布局 engine,默认值也不同
New Architecture / Hermes 自动解决性能必须在 release 版本定位具体 JS、UI、GPU、资源或网络瓶颈
Fast Refresh 后永远不用重新编译改 JS 常可刷新;改原生依赖、Codegen、配置或原生代码要 rebuild
React Native DevTools 替代原生 IDE原生崩溃和平台性能仍需要 Xcode / Android Studio
一次构建同时上双端iOS / Android 分别编译、签名、上传、测试和审核

延伸阅读#

参考资料#

本文共 11770 字,创建于 Aug 10, 2026

相关标签: Frontend, React, TypeScript, ByAI