iOS 开发中的 React Native

由浅入深,从基本概念到源码解析,带你全面掌握 React Native 在 iOS 平台的开发与应用


一、什么是 React Native?为什么选择它?

1.1 从 Hybrid 到 React Native

移动开发经历了从纯原生(Native)到 Hybrid(WebView)再到跨平台框架的演进:

方案 代表 优势 劣势
原生 Swift/ObjC 性能最佳、体验最好 双端重复开发
Hybrid Cordova、WebView 一套 HTML/JS 性能差、体验割裂
跨平台 React Native、Flutter 一套代码、接近原生 学习曲线、生态依赖

React Native (RN) 由 Meta 于 2015 年开源,核心理念是:用 JavaScript 编写逻辑,用原生组件渲染 UI,而不是在 WebView 中渲染。

1
2
传统 Hybrid:    JS/HTML → WebView 渲染 → 间接调用原生 API
React Native: JS/React → 虚拟 DOM → 原生组件(UILabel、UIView 等)直接渲染

1.2 为什么 iOS 开发者要学 React Native?

  • 业务需要:公司采用 RN 做跨端,需要维护/扩展原生能力
  • 原生桥接:RN 依赖大量原生模块(相机、蓝牙、支付等),需要 iOS 侧配合开发
  • 性能优化:理解 RN 与原生通信机制,才能做性能调优和问题排查
  • 新架构:新架构大量使用 C++、JSI,与 iOS 底层结合更紧密

1.3 RN 与 Flutter 的简要对比

维度 React Native Flutter
语言 JavaScript/TypeScript Dart
渲染 原生组件 自绘引擎(Skia)
包体积 相对较小 相对较大
生态 依赖 React、npm 独立生态
与原生交互 通过 Bridge/JSI 通过 Platform Channel

二、核心概念与架构

2.1 三层架构概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────────────────────┐
│ JavaScript 层 │
│ React 组件、业务逻辑、状态管理、事件处理 │
└──────────────────────────┬──────────────────────────────┘
│ Bridge / JSI
┌──────────────────────────▼──────────────────────────────┐
│ C++ 层(新架构) │
│ JSI、Fabric 渲染、TurboModules 调度 │
└──────────────────────────┬──────────────────────────────┘
│ FFI / Objective-C++
┌──────────────────────────▼──────────────────────────────┐
│ Native 层(iOS) │
│ UIKit、系统 API、自定义原生模块 │
└─────────────────────────────────────────────────────────┘

2.2 关键概念

概念 说明
Bridge 旧架构中 JS 与 Native 的异步通信桥梁,数据需序列化
JSI JavaScript Interface,新架构中 JS 可直接持有 C++ 对象引用,同步调用
Fabric 新架构的渲染系统,将布局、绘制逻辑下沉到 C++
TurboModules 新架构的原生模块系统,懒加载、类型安全
Hermes 字节码引擎,替代 JavaScriptCore,提升启动与运行性能

2.3 旧架构 vs 新架构

维度 旧架构 新架构
通信 Bridge 异步、JSON 序列化 JSI 同步、直接引用
渲染 各平台各自实现 Fabric 统一 C++ 渲染管线
原生模块 Native Modules 启动时全量加载 TurboModules 按需懒加载
类型 无强类型约定 通过 Codegen 生成类型

三、环境搭建与项目创建

3.1 环境要求

  • Node.js:建议 LTS 版本(18+)
  • Xcode:最新稳定版
  • CocoaPodsgem install cocoapods
  • Watchman(可选):brew install watchman,用于文件监听

3.2 创建新项目

1
2
3
4
5
6
7
8
9
10
11
# 使用 React Native CLI
npx @react-native-community/cli init MyApp

# 进入 iOS 目录
cd MyApp/ios

# 安装 CocoaPods 依赖
pod install

# 返回根目录,启动 Metro
cd .. && npx react-native start

另开终端运行 iOS:

1
2
3
npx react-native run-ios
# 或指定模拟器
npx react-native run-ios --simulator="iPhone 15"

3.3 项目结构(iOS 侧)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyApp/
├── ios/
│ ├── MyApp/ # 原生 iOS 工程
│ │ ├── AppDelegate.mm # 入口,加载 RN 根视图
│ │ ├── Info.plist
│ │ └── ...
│ ├── Podfile # CocoaPods 配置
│ ├── Podfile.lock
│ └── MyApp.xcworkspace # 用此打开工程
├── android/
├── src/ # JS 源码
├── node_modules/
├── package.json
└── metro.config.js

3.4 AppDelegate 与 RN 加载

典型的 AppDelegate.mm 中加载 RN 的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"MyApp"
initialProperties:nil
launchOptions:launchOptions];

self.window.rootViewController = [[UIViewController alloc] init];
self.window.rootViewController.view = rootView;
[self.window makeKeyAndVisible];
return YES;
}

RCTRootView 负责加载 JS Bundle、创建 Bridge、挂载 React 组件树。


四、JS 与 Native 通信原理

4.1 旧架构:Bridge 模型

旧架构下,JS 与 Native 通过 异步 Bridge 通信:

1
2
3
4
5
6
7
JS 层发起调用

├─ 将参数序列化为 JSON

├─ 通过 Bridge 发送到 Native 队列

└─ Native 解析 JSON,执行对应模块方法,再序列化结果回传 JS

特点

  • 异步:所有跨端调用都是异步的
  • 序列化:参数和返回值需要 JSON 序列化,有性能开销
  • 全量加载:所有 Native Modules 在启动时注册

4.2 旧架构模块注册流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 实现 RCTBridgeModule 协议
@interface MyNativeModule : NSObject <RCTBridgeModule>
@end

@implementation MyNativeModule

RCT_EXPORT_MODULE(); // 导出模块名,默认类名

RCT_EXPORT_METHOD(getDeviceId:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
NSString *id = [[UIDevice currentDevice] identifierForVendor].UUIDString;
resolve(id);
}

@end

JS 端调用:

1
2
3
4
import { NativeModules } from 'react-native';
const { MyNativeModule } = NativeModules;

const id = await MyNativeModule.getDeviceId();

4.3 新架构:JSI 直接调用

JSI 允许 JavaScript 直接持有 C++ 对象的引用,无需经过 Bridge 序列化:

1
2
3
4
5
6
7
8
// C++ 侧:通过 JSI 暴露方法
jsi::Function getDeviceId = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "getDeviceId"),
0,
[](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t) {
return jsi::String::createFromUtf8(rt, getNativeDeviceId());
});

JS 可直接同步调用,无需 Promise 包装。


五、新架构:JSI、Fabric、TurboModules

5.1 JSI(JavaScript Interface)

JSI 是 C++ 实现的薄封装层,让 JS 引擎(Hermes/JSC)能够:

  • 调用 C++ 函数
  • 读取/写入 C++ 对象属性
  • 在 C++ 中执行 JS 回调
1
2
3
4
5
6
7
8
9
// 简化示意:HostObject 暴露给 JS 的对象
class DeviceModule : public jsi::HostObject {
jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override {
if (name.utf8(rt) == "getDeviceId") {
return jsi::Function::createFromHostFunction(...);
}
return jsi::Value::undefined();
}
};

5.2 Fabric 渲染管线

Fabric 将 React 的渲染逻辑从各平台分别实现,统一到 C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
React 组件树


Shadow Tree(C++ 中的布局树)


布局计算(Yoga)


提交到原生层(Mount)


iOS UIView 创建/更新

优势:减少跨 Bridge 的序列化、支持同步布局、更好的并发与优先级调度。

5.3 TurboModules

TurboModules 的特性:

  • 懒加载:只在首次被 JS 引用时初始化
  • 类型安全:通过 Codegen 从 TypeScript 定义生成 C++ 和 ObjC 代码
  • 同步能力:通过 JSI 可实现同步调用

定义原生模块的规范(新架构):

1
2
3
4
5
6
7
8
9
10
// NativeMyModule.ts (Codegen 规范)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
getDeviceId(): Promise<string>;
multiply(a: number, b: number): number;
}

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

六、原生模块开发(Native Modules)

6.1 旧架构:RCT_EXPORT_MODULE

完整示例:实现一个获取设备信息的原生模块。

Objective-C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// DeviceInfoModule.h
#import <React/RCTBridgeModule.h>

@interface DeviceInfoModule : NSObject <RCTBridgeModule>
@end

// DeviceInfoModule.m
#import "DeviceInfoModule.h"
#import <React/RCTLog.h>
#import <UIKit/UIKit.h>

@implementation DeviceInfoModule

RCT_EXPORT_MODULE(DeviceInfo)

RCT_EXPORT_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *info = @{
@"model": [[UIDevice currentDevice] model],
@"systemVersion": [[UIDevice currentDevice] systemVersion],
@"name": [[UIDevice currentDevice] name],
};
resolve(info);
});
}

@end

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
import { NativeModules } from 'react-native';

const { DeviceInfo } = NativeModules;

async function loadDeviceInfo() {
try {
const info = await DeviceInfo.getDeviceInfo();
console.log(info);
} catch (e) {
console.error(e);
}
}

6.2 新架构:TurboModule + Swift

新架构推荐用 Swift 实现业务逻辑,用 ObjC++ 做 JSI 胶水层。

Swift 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// DeviceInfoModule.swift
import Foundation

@objc(DeviceInfoModule)
class DeviceInfoModule: NSObject {
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}

@objc
func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) {
let info: [String: Any] = [
"model": UIDevice.current.model,
"systemVersion": UIDevice.current.systemVersion
]
resolve(info)
}
}

通过 RCT_EXTERN_MODULE 导出给 ObjC:

1
2
3
4
5
6
7
8
9
// DeviceInfoModule.m(桥接)
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(DeviceInfoModule, NSObject)

RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

@end

6.3 事件发送:从 Native 到 JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在原生模块中
#import <React/RCTEventEmitter.h>

@interface MyModule : RCTEventEmitter <RCTBridgeModule>
@end

@implementation MyModule

RCT_EXPORT_MODULE()

- (NSArray<NSString *> *)supportedEvents {
return @[@"onScanResult"];
}

- (void)sendScanResult:(NSString *)result {
[self sendEventWithName:@"onScanResult" body:@{@"result": result}];
}

@end
1
2
3
4
5
6
import { NativeEventEmitter, NativeModules } from 'react-native';

const emitter = new NativeEventEmitter(NativeModules.MyModule);
emitter.addListener('onScanResult', (event) => {
console.log(event.result);
});

七、原生 UI 组件(Native UI Components)

7.1 使用 ViewManager 封装 UIView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// MyCustomViewManager.h
#import <React/RCTViewManager.h>

@interface MyCustomViewManager : RCTViewManager
@end

// MyCustomViewManager.m
#import "MyCustomViewManager.h"
#import "MyCustomView.h"

@implementation MyCustomViewManager

RCT_EXPORT_MODULE(MyCustomView)

- (UIView *)view {
return [[MyCustomView alloc] init];
}

RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)

@end
1
2
3
4
5
// MyCustomView.h
@interface MyCustomView : UIView
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@end

7.2 JS 侧使用

1
2
3
4
5
6
7
8
9
10
11
12
import { requireNativeComponent } from 'react-native';

const MyCustomView = requireNativeComponent('MyCustomView');

export default function Screen() {
return (
<MyCustomView
title="Hello"
onPress={(e) => console.log(e.nativeEvent)}
/>
);
}

7.3 新架构:Fabric 组件

新架构下,通过 Codegen 定义 Props 和事件,生成 C++ 与各平台代码,实现类型安全和更好的性能。


八、源码解析

8.1 初始化流程(iOS)

1
2
3
4
5
6
7
8
9
10
11
12
main()
└─ UIApplicationMain
└─ AppDelegate didFinishLaunchingWithOptions
└─ RCTRootView initWithBundleURL:moduleName:...
├─ 创建 RCTBridge
│ ├─ 加载 JavaScript Bundle
│ ├─ 初始化 JS 引擎(Hermes/JSC)
│ ├─ 注册所有 Native Modules
│ └─ 执行 JS 入口(AppRegistry.runApplication)

└─ 创建 RCTRootContentView
└─ 挂载 React 根组件,触发首次渲染

8.2 Bridge 核心结构(旧架构)

1
2
3
4
5
6
7
8
9
10
11
12
// 简化示意
@interface RCTBridge : NSObject
@property (nonatomic, strong) RCTBridge *batchedBridge; // 实际执行 Bridge
@end

// 模块调用流程
// JS: NativeModules.DeviceInfo.getDeviceInfo()
// -> __callFunction(moduleName, methodName, args)
// -> 序列化为 JSON,通过 RCTBridge 发送
// -> Native: RCTModuleData 根据 moduleName 找到模块实例
// -> 反序列化参数,invoke 对应方法
// -> 结果序列化回传 JS

8.3 新架构关键路径

  • JSIReactCommon/jsi/,提供 jsi::RuntimeHostObject
  • FabricReactCommon/react/renderer/,Shadow Tree、Mount、Component 定义
  • TurboModulesReactCommon/react/nativemodule/,模块注册与调用

可参考官方仓库:
https://github.com/facebook/react-native


九、实战示例

9.1 调用系统分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ShareModule.m
RCT_EXPORT_METHOD(share:(NSDictionary *)options
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *title = options[@"title"] ?: @"";
NSString *url = options[@"url"] ?: @"";

UIActivityViewController *activityVC = [[UIActivityViewController alloc]
initWithActivityItems:@[title, [NSURL URLWithString:url]]
applicationActivities:nil];

UIViewController *rootVC = [UIApplication sharedApplication]
.keyWindow.rootViewController;
[rootVC presentViewController:activityVC animated:YES completion:nil];
resolve(@YES);
});
}

9.2 封装原生 TabBar

在 RN 中嵌入 UITabBarController 的容器,通过 Native Module 控制 Tab 切换,实现与原生 TabBar 一致的外观和动效。

9.3 列表性能优化:FlashList

1
2
3
4
5
6
7
8
9
10
11
12
13
import { FlashList } from '@shopify/flash-list';

function ProductList({ data }) {
const renderItem = ({ item }) => <ProductCard item={item} />;

return (
<FlashList
data={data}
renderItem={renderItem}
estimatedItemSize={100}
/>
);
}

FlashList 使用按需渲染和复用,比 FlatList 更适合长列表场景。


十、实际项目中的应用案例

10.1 电商 App:商品详情混合栈

  • Native:顶部 Banner 轮播、视频播放、复杂动效
  • RN:评价列表、推荐列表、加购/下单逻辑

通过 RCTRootView 嵌入到 UIViewController 的指定区域,实现「上原生、下 RN」的混合页面。

10.2 金融 App:安全键盘

输入密码时使用 Native 自定义键盘(避免 RN 侧键盘被截屏/录屏),通过 Native Module 将输入结果回传 JS:

1
2
3
4
5
6
7
8
RCT_EXPORT_METHOD(showSecureKeyboard:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
SecureKeyboardViewController *vc = [[SecureKeyboardViewController alloc]
initWithCompletion:^(NSString *pin) {
resolve(pin);
}];
[self presentVC:vc];
}

10.3 地图与 LBS

地图、路径规划、定位等使用 Native SDK,通过 Native UI Component 和 Native Module 暴露给 RN,兼顾性能与功能完整性。

10.4 OTA 热更新

将打包好的 JS Bundle 下发到本地,启动时优先加载本地 Bundle,实现不发版即可更新业务逻辑(需注意各应用市场的合规要求)。


十一、常见问题与最佳实践

11.1 主线程与 UI 更新

原生模块中涉及 UI 的操作必须回到主线程:

1
2
3
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:vc animated:YES completion:nil];
});

11.2 避免内存泄漏

  • 使用 RCTEventEmitter 时正确实现 invalidate
  • Block 中使用 weakSelf 避免循环引用
  • 大对象及时释放,避免长期持有

11.3 调试技巧

1
2
3
4
5
6
7
8
# 查看 Metro 日志
npx react-native start

# 真机调试
npx react-native run-ios --device

# 查看原生日志
# Xcode -> Debug -> Open System Log,或使用 Console.app

11.4 性能建议

  • 长列表使用 FlashList 或优化 FlatListgetItemLayout
  • 复杂动画考虑 react-native-reanimated
  • 新项目尽量启用新架构(Fabric + TurboModules)
  • 图片使用 FastImage 或自定义 Native 图片组件做缓存

11.5 启用新架构

ios/Podfile 中:

1
ENV['RCT_NEW_ARCH_ENABLED'] = '1'

执行 pod install 后重新编译。


十二、总结

场景 建议
新项目 启用新架构(Fabric + TurboModules + Hermes)
原生能力扩展 通过 Native Module 暴露,优先用 Swift + ObjC 桥接
复杂 UI 封装 Native UI Component,或使用成熟第三方组件
性能敏感 长列表用 FlashList,动画用 Reanimated
调试 Metro + Flipper + Xcode 结合使用

React Native 在 iOS 上的核心价值是:用 React 生态统一业务逻辑,用原生能力保证体验与性能。理解 Bridge/JSI、Fabric、TurboModules 的演进,有助于在混合栈项目中做出更合适的架构与实现选择。

Author

Felix Tao

Posted on

2021-04-16

Updated on

2023-02-11

Licensed under