React NativeExpoMetroJSITurboModulesFabricCodegenHermesMobile Development

React Native + Expo + Metro + 原生实现完全解析

Sloth255
Sloth255
·6 min read·1,284 words

刚开始使用 React Native 时,很容易产生"不知道为什么就跑起来了"的感觉。本文将从 Metro、JSI、Codegen、Expo 的内部运作机制,到 Expo SDK 无法覆盖的原生实现领域,进行一体化的深度解析。


1. React Native 的核心理念

React Native 的核心在于将用 JS 编写的 UI 逻辑转换为原生 UI 组件。就像 ReactDOM 将 <div> 转换为 DOM 元素一样,React Native 将 <View><Text> 映射到 iOS 的 UIView 和 Android 的 android.view.View

也就是说,它生成的是真正的原生 UI,而不是在 WebView 中渲染 HTML。这是与 Cordova 或 Ionic 的根本区别。


2. 架构演进:从 Bridge 到 JSI

React Native 目前存在两种并行的架构。

flowchart LR
  subgraph OLD["旧架构(Bridge)"]
    direction TB
    J1["JS 线程<br/>React 逻辑"] -->|"JSON 序列化"| BR["Bridge<br/>异步队列 ⚠"]
    BR -->|"JSON 反序列化"| N1["原生线程<br/>UIKit / Android View"]
    SH["Shadow 线程<br/>Yoga 布局"] --> N1
  end
  subgraph NEW["新架构(JSI)"]
    direction TB
    HM["Hermes 引擎<br/>字节码执行"] -->|"直接 C++ 引用"| JSI["JSI<br/>Host Objects"]
    JSI --> FAB["Fabric<br/>同步渲染器"]
    JSI --> TM["TurboModules<br/>懒加载初始化"]
    FAB --> N2["原生层"]
    TM --> N2
  end

旧架构(Bridge)的问题

旧版 React Native 的 JS 线程和原生线程通过 Bridge 这一异步的单点瓶颈进行通信。所有交互都需要序列化为 JSON 字符串、发送、再反序列化,这一往返过程导致动画、手势等高频操作容易出现性能问题。

新架构(JSI + Hermes)的革新

JSI(JavaScript Interface)是一个用 C++ 实现的轻量绑定层,使 JS 引擎能够直接同步引用原生对象。这带来了以下变革:

  • Fabric — 可同步构建和更新 UI 树的新渲染器
  • TurboModules — 延迟初始化,直到需要时才加载原生模块
  • Hermes — Facebook 开发的引擎,将 JS 预编译为字节码,大幅缩短启动时间

3. Codegen:类型安全原生通信的核心

Codegen 是新架构中最容易被忽视的机制。JSI 和 TurboModules 让通信成为可能,而 Codegen 则在构建时保证通信正确无误

flowchart TD
  SPEC["JS Spec 文件<br/>用 TypeScript / Flow 定义类型"]

  subgraph BUILD["构建时"]
    CG["Codegen<br/>在 pod install / Gradle 时执行"]
  end

  subgraph OUT["生成的产物"]
    CPP["C++ 抽象类<br/>TurboModule 基础接口"]
    DESC["Component Descriptor<br/>用于 Fabric 原生组件"]
    JSTYPE["JS 类型定义<br/>等同于 .d.ts"]
  end

  subgraph NATIVE["原生实现"]
    IOS["iOS<br/>使用 Swift / Obj-C 实现"]
    AND["Android<br/>使用 Kotlin / Java 实现"]
  end

  RUNTIME["通过 JSI<br/>类型安全、同步调用"]

  SPEC --> CG
  CG --> CPP & DESC & JSTYPE
  CPP --> IOS & AND
  DESC --> IOS & AND
  IOS --> RUNTIME
  AND --> RUNTIME

在旧架构中,JS 与原生的接口几乎是"君子协定"。从 JS 调用 NativeModules.MyModule.doSomething(42),如果原生期望接收 String,直到运行时崩溃才会发现问题。

Codegen 在构建时解决了这个问题。开发者只需用 TypeScript 或 Flow 编写 Spec(规格说明),描述原生模块接收何种类型。在 pod install(iOS)或 Gradle 构建(Android)时,Codegen 自动生成 C++ 抽象类iOS 和 Android 的原生模板代码以及 JS 侧的类型定义


4. Metro Bundler 的处理流程

Metro 是 React Native 专用的 JavaScript 打包器。它与 webpack 承担相同的角色,但针对移动端开发进行了优化

flowchart LR
  SRC[".js / .ts / .tsx<br/>源文件"]
  RES["解析<br/>import 路径解析"]
  TRA["转换<br/>Babel ES 转换"]
  SER["序列化<br/>生成 Bundle"]
  DEV["发送到设备<br/>HTTP :8081"]
  HMR["HMR<br/>仅重发变更模块"]
  CACHE["文件系统缓存<br/>第二次起速度更快"]

  SRC --> RES --> TRA --> SER --> DEV
  TRA -.->|"检测文件变更"| HMR
  HMR -.->|"保留状态实时反映"| DEV
  TRA <-.->|"缓存转换结果"| CACHE

Metro 的处理分为三个阶段:

  • 解析 — 类似 Node.js 的模块解析,自动选择 foo.ios.ts / foo.android.ts 等平台特定文件
  • 转换 — Babel 转换 JSX、TypeScript 和现代 ES;结果缓存到文件系统,后续启动更快
  • 序列化 — 生成最终 Bundle,通过 HTTP 服务器发送到设备

值得注意的是 HMR(热模块替换)。只更新发生变更的模块图谱,可以在保留 React 组件状态的同时立即反映代码变更。生产构建将 --minify 与 Hermes 字节码编译结合,同时减小文件大小和启动时间。


5. Expo 的层次结构与开发工具

flowchart TD
  APP["你的应用<br/>App.tsx / screens / hooks"]
  SDK["Expo SDK<br/>expo-camera / expo-location / expo-router …"]
  EMC["expo-modules-core<br/>Swift / Kotlin DSL 类型安全描述"]
  RN["React Native Core<br/>View / Text / Animated / StyleSheet"]
  IOS["iOS<br/>UIKit / AVFoundation / CoreLocation"]
  AND["Android<br/>View / Camera2 / LocationManager"]
  GO["Expo Go<br/>扫描二维码即可启动"]
  EASB["EAS Build<br/>在云端生成 .ipa / .apk"]
  EASU["EAS Update<br/>通过 OTA 仅发布 JS Bundle"]
  STORE["App Store / Google Play"]

  APP --> SDK
  EMC --> SDK
  SDK --> RN
  RN --> IOS & AND
  GO -->|"开发时托管"| APP
  EASB --> STORE
  EASU -->|"无需 App Store 审核"| APP

Expo SDK 将相机、位置、通知、文件系统等原生功能以版本化包的形式提供。无需自行编写原生代码,这是它最大的价值所在。

expo-modules-core 是一种较新的机制,使用 Swift 和 Kotlin DSL 可以声明式地编写原生模块,并与 Codegen 集成。比旧的 NativeModules 桥接方式更类型安全、代码量更少,Expo 的几乎所有第三方 SDK 都使用它构建。

EAS(Expo Application Services) 是 Build、Update、Submit、Workflows 等多种服务的总称。本文主要介绍 EAS BuildEAS Update。EAS Build 在云端构建原生二进制文件,无需 Mac 即可完成 iOS 构建。EAS Update 通过 OTA 发布 JS Bundle 和资产,可快速推送不涉及原生代码的修改。


6. Expo 无法覆盖的领域与工作流选择

Expo SDK 主要无法覆盖以下领域:

  • 蓝牙 LE / NFC — 平台特定的外设通信
  • 自定义支付/生物识别 SDK — 直接集成供应商 .framework / .aar
  • 实时音视频处理 — WebRTC 或自定义编解码器的底层操作
  • 内部原生库 — 从现有应用中提取的 Swift / Kotlin 资产
  • 自定义相机视图 — 使用 OpenGL / Metal 的自定义渲染
flowchart TD
  START["需要什么原生功能?"]

  START --> Q1{"Expo SDK / 公开库<br/>能否覆盖?"}
  Q1 -->|"是"| MANAGED["Managed Workflow<br/>无需原生代码<br/>只需 npx expo start"]
  Q1 -->|"否"| Q2{"只需配置变更?<br/>(Info.plist / AndroidManifest 追加等)"}
  Q2 -->|"是"| PLUGIN["创建 Config Plugin<br/>追加到 app.json<br/>prebuild 自动应用"]
  Q2 -->|"否"| Q3{"自定义原生代码的规模?"}
  Q3 -->|"轻量<br/>(封装现有 SDK)"| BARE["Bare Workflow<br/>npx expo prebuild<br/>→ 直接管理 ios/ android/"]
  Q3 -->|"大规模<br/>(自定义模块 / UI)"| MODULE{"构建什么?"}
  MODULE -->|"逻辑类<br/>蓝牙 / 支付 / 加密"| TM["TurboModule<br/>+ Codegen"]
  MODULE -->|"UI 组件类<br/>自定义相机视图等"| FAB["Fabric Component<br/>+ Codegen"]
  BARE --> TM & FAB
  PLUGIN --> BARE

轻微的配置变更可以用 Config Plugin 处理。但如果需要编写代码,通过 npx expo prebuild 生成 ios/android/ 目录并迁移到 Bare Workflow,是现实的起点。


7. 整体架构:JSI + Codegen + 原生实现

flowchart TB
  subgraph JS["JS 层(Hermes)"]
    APP["应用代码 / React 组件"]
    SPEC["JS Spec<br/>(TypeScript + TurboModuleRegistry)"]
  end

  subgraph CODEGEN["Codegen(构建时自动生成)"]
    GEN_CPP["C++ 抽象类<br/>TurboModule / ComponentDescriptor"]
    GEN_SHADOW["ShadowNode / Props 定义"]
  end

  subgraph JSI_LAYER["JSI 层(C++)"]
    JSI["JSI Host Object"]
    FABRIC["Fabric 渲染器"]
  end

  subgraph NATIVE_IOS["iOS 原生实现"]
    SWIFT["Swift / Obj-C 实现类<br/>(继承 C++ 抽象类)"]
    SDK_IOS["内部 SDK / 外部 .framework<br/>如 CoreBluetooth / Stripe"]
    UIVIEW["UIView 子类<br/>(自定义 UI 组件)"]
  end

  subgraph NATIVE_AND["Android 原生实现"]
    KOTLIN["Kotlin / Java 实现类<br/>(继承 C++ 抽象类)"]
    SDK_AND["内部 SDK / 外部 .aar<br/>如 BluetoothGatt / Stripe"]
    ANDROIDVIEW["View 子类<br/>(自定义 UI 组件)"]
  end

  SPEC -->|"读取类型定义"| CODEGEN
  GEN_CPP --> JSI
  GEN_SHADOW --> FABRIC
  APP -->|"同步 / 异步调用"| JSI
  JSI --> SWIFT & KOTLIN
  FABRIC --> UIVIEW & ANDROIDVIEW
  SWIFT --> SDK_IOS
  KOTLIN --> SDK_AND

关键在于 Spec → Codegen → 原生实现这一单向依赖关系。在 JS 侧编写类型定义,Codegen 在构建时生成 C++ 抽象类,Swift / Kotlin 实现类只需继承即可,类型一致性由编译器保证,"君子协定"导致的运行时崩溃随之消失。


8. TurboModule 实现流程(以蓝牙为例)

注:以下代码是基于 React Native 官方 TurboModule / Codegen / Custom Events 指南的简化示例,并非直接引用官方示例,而是以蓝牙为题材重新构建。

① 编写 JS Spec

// src/specs/NativeBluetoothModule.ts
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export type ScanResult = {
  id: string;
  name: string;
  rssi: number;
};

export interface Spec extends TurboModule {
  startScan(serviceUUIDs: string[]): void;
  stopScan(): void;
  connect(deviceId: string): Promise<boolean>;
  readonly onDeviceFound: CodegenTypes.EventEmitter<ScanResult>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeBluetoothModule');

② Codegen 自动生成的内容

flowchart LR
  SPEC["NativeBluetoothModule.ts<br/>(开发者编写)"]

  subgraph AUTO["自动生成的代码"]
    CPP["C++ glue code<br/>JSI / Codegen 生成物"]
    IOS_H["iOS glue code<br/>Obj-C++ / header 生成物"]
    AND_J["Android glue code<br/>JNI / Spec 类生成物"]
  end

  subgraph IMPL["开发者实现"]
    SWIFT_IMPL["NativeBluetoothModule.swift<br/>实现生成的 Spec,调用 CoreBluetooth"]
    KOTLIN_IMPL["NativeBluetoothModule.kt<br/>实现生成的 Spec,调用 BluetoothGatt"]
  end

  SPEC --> CPP
  SPEC --> IOS_H
  SPEC --> AND_J
  CPP --> SWIFT_IMPL & KOTLIN_IMPL
  IOS_H --> SWIFT_IMPL
  AND_J --> KOTLIN_IMPL

③ iOS 实现(Swift)

// ios/NativeBluetoothModule.swift
import CoreBluetooth

@objc(NativeBluetoothModule)
class NativeBluetoothModule: NativeBluetoothModuleSpec, CBCentralManagerDelegate {
  private var centralManager: CBCentralManager!

  override func startScan(_ serviceUUIDs: [String]) {
    let uuids = serviceUUIDs.map { CBUUID(string: $0) }
    centralManager.scanForPeripherals(withServices: uuids)
  }

  func centralManager(_ central: CBCentralManager,
                      didDiscover peripheral: CBPeripheral,
                      advertisementData: [String: Any], rssi: NSNumber) {
    emitOnDeviceFound([
      "id": peripheral.identifier.uuidString,
      "name": peripheral.name ?? "",
      "rssi": rssi
    ])
  }
}

④ Android 实现(Kotlin)

// android/src/main/java/NativeBluetoothModule.kt
class NativeBluetoothModule(reactContext: ReactApplicationContext)
    : NativeBluetoothModuleSpec(reactContext), BluetoothGatt.Callback {

  private val adapter = BluetoothAdapter.getDefaultAdapter()

  override fun startScan(serviceUUIDs: ReadableArray) {
    val filters = serviceUUIDs.toArrayList().map { uuid ->
      ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid as String)).build()
    }
    adapter.bluetoothLeScanner.startScan(filters, ScanSettings.Builder().build(), scanCallback)
  }

  private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
      val params = Arguments.createMap().apply {
        putString("id", result.device.address)
        putString("name", result.device.name ?: "")
        putInt("rssi", result.rssi)
      }
      emitOnDeviceFound(params)
    }
  }
}

9. Fabric Component 实现流程(自定义相机视图示例)

如果 TurboModule 负责逻辑,Fabric Component 则用于将原生 UI 嵌入 JSX。使用 OpenGL / Metal 进行自定义渲染,或直接复用现有原生应用中的 View 时需要用到它。

注:以下也是用于理解的最小示例。Codegen 的 import 路径和类型名会因 React Native 版本而异,实现时请参考所用版本的官方文档。

// src/specs/NativeCameraViewSpec.ts
import type { ViewProps } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { DirectEventHandler, Float } from 'react-native/Libraries/Types/CodegenTypes';

type FrameCapturedEvent = Readonly<{
  uri: string;
}>;

interface NativeProps extends ViewProps {
  zoom?: Float;          // Codegen 将 Float 转换为 CGFloat / float
  torchEnabled?: boolean;
  onFrameCaptured?: DirectEventHandler<FrameCapturedEvent>;
}

export default codegenNativeComponent<NativeProps>('CameraView');

Codegen 从这个 Spec 生成 ShadowNodeComponentDescriptorProps 的 C++ 定义。iOS 实现继承 RCTViewComponentViewCameraViewComponentView,Android 继承 ReactViewGroup。在 JS 侧只需 <CameraView zoom={2.0} torchEnabled /> 即可使用。


10. 集成内部 SDK / 外部 .framework.aar

iOS:通过 CocoaPods 本地引用

# ios/Podfile
pod 'CompanyPaymentSDK', :path => '../vendor/ios/CompanyPaymentSDK'
# 或 xcframework 的情况
pod 'CompanyPaymentSDK', :podspec => '../vendor/ios/CompanyPaymentSDK.podspec'

添加到 Podfile 并运行 pod install 后,可以像普通 Swift 类一样通过 import CompanyPaymentSDK 使用,直接从 TurboModule 实现类中调用即可。

Android:本地 Maven / .aar 引用

// android/app/build.gradle
dependencies {
  implementation files('../vendor/android/company-payment-sdk.aar')
  // 或内部 Maven 仓库
  implementation 'com.company:payment-sdk:1.4.0'
}

11. 实际开发循环:从启动到验证

flowchart TD
  INIT["npx create-expo-app MyApp<br/>--template blank-typescript"]

  subgraph START["npx expo start"]
    METRO["Metro Bundler 启动<br/>port 8081"]
    QR["显示二维码 + 快捷键<br/>i = iOS  a = Android  w = Web"]
  end

  subgraph DEVICE["选择目标设备"]
    SIM["iOS Simulator<br/>Xcode 自带(仅 Mac)"]
    EMU["Android Emulator<br/>Android Studio AVD"]
    GO["真机 + Expo Go<br/>局域网或 Tunnel"]
  end

  subgraph NATIVE_BUILD["仅首次:原生构建"]
    POD["pod install<br/>iOS CocoaPods"]
    GRADLE["Gradle build<br/>Android"]
    CODEGEN_RUN["Codegen 执行<br/>Spec → 原生模板"]
  end

  BUNDLE["JS Bundle 传输到设备<br/>通过 Metro HTTP 服务器"]
  RENDER["画面显示"]

  INIT --> START
  METRO --> QR
  QR --> SIM & EMU & GO
  SIM --> NATIVE_BUILD
  EMU --> NATIVE_BUILD
  NATIVE_BUILD --> BUNDLE
  GO --> BUNDLE
  BUNDLE --> RENDER

运行 npx expo start 后 Metro 启动,终端显示二维码和键盘快捷键。

  • i 键 → iOS Simulator(在安装了 Xcode 的 Mac 上运行)
  • a 键 → Android Emulator(在 Android Studio AVD Manager 中创建的虚拟设备)
  • 真机可用 Expo Go 扫描二维码,局域网内几乎瞬间连接

使用 npx expo run:iosnpx expo run:android 明确触发原生构建时,首次会执行 pod install(iOS)或 Gradle(Android),其中 Codegen 也会运行并生成原生模板代码。第二次起只传输 JS Bundle 差异,速度较快。


12. Bare Workflow 的变更循环

flowchart TD
  subgraph CHANGE["按变更类型分支"]
    JS_ONLY["仅 JS 变更<br/>组件 / 逻辑 / 样式"]
    NATIVE_CHANGE["原生代码变更<br/>Swift / Kotlin / Spec / Podfile"]
  end

  subgraph JS_LOOP["JS 变更循环(快速)"]
    METRO_HMR["Metro HMR<br/>传输差异 Bundle<br/>数百毫秒"]
    SIM_JS["模拟器即时反映<br/>保留应用状态"]
  end

  subgraph NATIVE_LOOP["原生变更循环(慢)"]
    CODEGEN_RUN["重新执行 Codegen<br/>pod install (iOS)<br/>Gradle sync (Android)"]
    REBUILD["原生重新构建<br/>npx expo run:ios<br/>npx expo run:android"]
    SIM_NATIVE["在模拟器 / 真机上确认"]
  end

  JS_ONLY --> METRO_HMR --> SIM_JS
  NATIVE_CHANGE --> CODEGEN_RUN --> REBUILD --> SIM_NATIVE

  SIM_JS -->|"没有问题"| DONE["验证完成"]
  SIM_NATIVE -->|"没有问题"| DONE
  SIM_JS -->|"发现 Bug"| DEBUG_JS["Hermes Debugger<br/>React DevTools"]
  SIM_NATIVE -->|"原生崩溃"| DEBUG_NATIVE["Xcode / Android Studio<br/>原生调试器"]
  DEBUG_JS --> JS_ONLY
  DEBUG_NATIVE --> NATIVE_CHANGE

仅 JS 变更时,Metro HMR 在数百毫秒内传输差异,状态也得以保留。

原生代码发生变更时需要重新构建。iOS 首次构建约需 2〜5 分钟,差异构建约 30 秒〜1 分钟。Android 在 Gradle 缓存生效的情况下类似,首次构建耗时更长。

这种不对称性是 Bare Workflow 开发效率的关键。预先确定原生边界(Spec),将大部分实现放在 JS 侧,可以降低频繁触发重量级原生构建的频率。


13. 调试:跨越 JS 与原生边界的问题排查

flowchart TD
  SYMPTOM["观察症状"]

  SYMPTOM --> Q1{"崩溃类型"}

  Q1 -->|"JS 错误<br/>LogBox 红屏"| JS_ERR["用 Hermes Debugger 调查<br/>断点、堆栈跟踪<br/>Source Map 显示 TS 行号"]
  Q1 -->|"原生崩溃<br/>应用崩溃 / 冻结"| NAT_ERR["检查崩溃日志"]
  Q1 -->|"能运行但<br/>结果不正确"| BORDER["怀疑 JS↔Native 边界"]

  NAT_ERR --> IOS_CRASH["iOS: Xcode<br/>Debug Navigator<br/>+ lldb 附加"]
  NAT_ERR --> AND_CRASH["Android: Android Studio<br/>Logcat 堆栈跟踪<br/>+ Native Debugger"]

  BORDER --> LOG_IOS["iOS: NSLog / os_log<br/>npx react-native log-ios"]
  BORDER --> LOG_AND["Android: Log.d<br/>npx react-native log-android"]
  BORDER --> JS_LOG["JS 侧: console.log<br/>确认 TurboModule 返回值"]

  IOS_CRASH --> INSTRUMENTS["Instruments<br/>Time Profiler / Allocations<br/>定位内存泄漏 / CPU"]
  AND_CRASH --> PROFILER["Android Studio<br/>Memory / CPU Profiler"]

  JS_ERR --> FIXED["修复 → Metro HMR 即时反映"]
  INSTRUMENTS --> FIXED2["修复 → npx expo run:ios 确认"]
  PROFILER --> FIXED3["修复 → npx expo run:android 确认"]
  LOG_IOS & LOG_AND & JS_LOG --> BORDER2["定位根本原因<br/>→ 修复 Spec / 实现"]

JS 错误:Hermes Debugger

Cmd+D → "Open Debugger" 打开 Chrome DevTools。Metro 提供 Source Map,可以在原始 TypeScript 行号而非打包后的代码上设置断点,TurboModule 调用中发生的异常也能追踪到 JS 侧的调用堆栈。

原生崩溃:附加 Xcode / Android Studio 调试器

# iOS: 将 lldb 附加到运行中的模拟器
npx expo run:ios --configuration Debug
# → Xcode 自动打开并附加调试器

# Android
npx expo run:android
# → "Run > Attach Debugger to Android Process" 附加

对于只在真机上复现的崩溃(蓝牙、相机、传感器类尤其如此),可以用同样的方式将调试器附加到通过 USB 连接的真机上。

边界调查:用日志夹击

当 JS↔Native 边界的数据出现异常时,从两侧用日志夹击是最快的方法。

// Swift 侧
os_log("BluetoothModule.connect called: %{public}@", deviceId)
// → 用 npx react-native log-ios 过滤查看
// Kotlin 侧
Log.d("BluetoothModule", "connect called: $deviceId")
// → 用 npx react-native log-android 过滤查看
// JS 侧
const result = await BluetoothModule.connect(deviceId);
console.log('connect result:', result); // 输出到 Metro

React DevTools

npx react-devtools
# → 在端口 8097 等待设备连接

可视化组件树,直接在检查器中修改 props 和 state,实时确认效果。


14. 模拟器与真机的使用区分

验证内容 iOS Simulator Android Emulator 真机
UI / 布局 足够 足够 最终确认
JS 逻辑 / API 足够 足够 不需要
蓝牙 / NFC 不可用 不可用 必须
相机 / 麦克风 部分可用 不可用 必须
推送通知 部分可用 部分可用 推荐
性能 仅供参考 仅供参考 必须
生物识别 可模拟 Face ID 可模拟指纹 推荐

涉及原生模块的开发,实用的两阶段方案是:在模拟器上固化 JS 行为,在真机上验证原生功能。蓝牙和 NFC 完全无法在模拟器上测试,因此真机数量和 OS 版本覆盖率直接影响质量。


总结

技术栈全貌

层次 技术 作用
打包器 Metro 源代码转译、HMR、平台分支
JS 引擎 Hermes 字节码执行以加速启动
JS↔Native 桥接 JSI 通过 C++ 的同步绑定
代码生成 Codegen 从 Spec 自动生成类型安全的原生模板
UI 渲染器 Fabric 基于 JSI 的同步 UI 渲染
原生 API TurboModules 继承 Codegen 生成类的懒加载模块
开发基础设施 Expo / EAS SDK、云端构建、OTA 更新

涉及原生实现的开发全流程

① 编写 JS Spec(TypeScript 类型定义)
       ↓
② pod install / gradle build → Codegen 自动生成 C++ 模板
       ↓
③ 用 Swift / Kotlin 实现(调用内部 SDK / 外部库)
       ↓
④ npx expo run:ios / run:android 原生构建 & 模拟器验证
       ↓
⑤ 编写 JS 调用代码 → 通过 Metro HMR 快速迭代
       ↓
⑥ JS 错误 → Hermes Debugger
   原生崩溃 → Xcode / Android Studio 调试器
   边界不一致 → 用 log-ios / log-android + console.log 夹击
       ↓
⑦ 在真机上验证蓝牙 / 相机 / 传感器等功能
       ↓
⑧ EAS Build 生成应用商店发布的二进制文件

从 Bridge 迁移到 JSI + Codegen,React Native 完成了从"能跑但脆弱"到"类型安全、高性能"的蜕变。迈出 Expo 的范围会带来构建耗时增加、两种语言编写相同逻辑等成本,但 Codegen 让 Spec 成为唯一的真实来源,在设计阶段就消除了由类型不匹配导致运行时崩溃这一最大风险。


参考链接

注:本文基于以下资料进行了总结和重构。文中说明和代码示例为便于理解做了简化,并非直接引用。实际实现时请优先参考所用版本的官方文档。

参考 URL

React Native 官方

Expo 官方