🚀 React Native 新架构实战:从零实现 TurboModule + Fabric Component(含 iOS 原生容器嵌入)
关键词:React Native 0.86、New Architecture、TurboModule、Fabric Component、Codegen、C++、iOS 容器嵌入
前言
2024 年 React Native 正式将新架构设为默认,TurboModule 取代了 Bridge 通信模式,Fabric 取代了 UIManager 渲染管线。然而市面上的实战教程大多停留在理论层面,本文将带你从 零代码 出发,一步步落地:
- ✅ TurboModule:一个 JS ↔ Native 双向通信的计数器模块
- ✅ Fabric Component :一个支持
color+cornerRadius的原生彩色视图 - ✅ iOS 原生容器:在纯 iOS App 中嵌入 RN 页面并跑通新架构
- ✅ 双端实现:完整覆盖 iOS (ObjC++ / Swift) 与 Android (Kotlin)
📁 项目结构总览
bash
21DaysALLIN/
├── MyRNModule/ # React Native App(JS 层)
│ ├── specs/ # Codegen 接口定义
│ │ ├── NativeCounter.ts # TurboModule 类型定义
│ │ └── NativeColoredView.ts # Fabric Component 类型定义
│ ├── App.tsx # Demo UI
│ ├── package.json # codegenConfig 配置
│ ├── ios/Podfile # RCT_NEW_ARCH_ENABLED=1
│ └── android/app/src/main/java/com/myrnmodule/
│ ├── CounterTurboModule.kt # Android TurboModule 实现
│ ├── ColoredViewManager.kt # Android Fabric Component 实现
│ └── MyRNPackage.kt # 注册入口
│
└── IOSRNContainer/ # iOS 原生容器 Demo
├── IOSRNContainer/ # Swift 入口
│ ├── AppDelegate.swift # 启动时 bootstrap RN
│ ├── ReactNativeHost.swift # RNSingleton 管理工厂
│ ├── ReactNativeViewController.swift # VC 包装器
│ └── ViewController.swift # 原生首页
├── IOSRNModule/ # Native 实现层
│ ├── NativeColoredView.h/mm # Fabric 组件(C++)
│ ├── CounterTurboModule.h/mm# TurboModule(ObjC++)
│ └── CodegenHeaders/ # Codegen 自动生成的头文件
└── Podfile # 编译配置 + Codegen header 注入
第一步:JS Spec 定义(Codegen 的接口契约)
新架构的核心思路是 用 TypeScript 描述接口,Codegen 自动生成 C++ 胶水代码。我们先定义两个 TS Spec:
TurboModule Spec --- specs/NativeCounter.ts
typescript
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
import type { Double } from 'react-native/Libraries/Types/CodegenTypes';
export interface Spec extends TurboModule {
getValue(): Promise<Double>;
increment(step: Double): Promise<Double>;
decrement(step: Double): Promise<Double>;
reset(): Promise<void>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeCounter');
要点 :TurboModule 的返回类型必须是
Promise<>,数值类型用Double(Codegen 专门类型),而非number。
Fabric Component Spec --- specs/NativeColoredView.ts
typescript
import type { ViewProps } from 'react-native';
import { codegenNativeComponent } from 'react-native';
import type { HostComponent } from 'react-native';
import type { Double } from 'react-native/Libraries/Types/CodegenTypes';
export interface NativeProps extends ViewProps {
color?: string;
cornerRadius?: Double;
}
export default codegenNativeComponent<NativeProps>(
'NativeColoredView',
) as HostComponent<NativeProps>;
要点 :Fabric Component 继承
ViewProps,使用codegenNativeComponent而不是requireNativeComponent。
第二步:配置 Codegen(package.json 中的 codegenConfig)
json
{
"codegenConfig": {
"name": "MyRNAppSpecs",
"type": "all",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.myrnmodule"
},
"ios": {
"componentProvider": {
"NativeColoredView": "NativeColoredView"
},
"modulesProvider": {
"NativeCounter": "CounterTurboModule"
}
}
}
}
字段说明:
| 字段 | 含义 |
|---|---|
name |
Codegen 输出的命名空间,iOS 为 #import <MyRNAppSpecs/MyRNAppSpecs.h> |
type |
"all" 表示同时生成 TurboModule + Fabric Component 代码 |
jsSrcsDir |
TS Spec 文件所在的目录 |
ios.componentProvider |
Fabric Component 名称 → 原生类名的映射 |
ios.modulesProvider |
TurboModule JS 名称 → 原生类名的映射 |
第三步:iOS 原生实现(ObjC++ + Fabric C++ API)
3.1 开启 New Architecture
在 MyRNModule/ios/Podfile 中:
ruby
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
执行 pod install 后,Codegen 会在 Pods 中自动生成头文件 MyRNAppSpecs.h。
3.2 Fabric Component 实现 --- NativeColoredView.mm
Fabric Component 的核心是继承 RCTViewComponentView,并使用 C++ Props 对象来接收属性更新:
objc
#import "NativeColoredView.h"
#import <react/renderer/components/MyRNAppSpecs/ComponentDescriptors.h>
#import <react/renderer/components/MyRNAppSpecs/Props.h>
#import <react/renderer/components/MyRNAppSpecs/RCTComponentViewHelpers.h>
using namespace facebook::react;
@implementation NativeColoredView
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<NativeColoredViewComponentDescriptor>();
}
- (void)updateProps:(const Props::Shared &)props
oldProps:(const Props::Shared &)oldProps {
const auto &newViewProps = *std::static_pointer_cast<const NativeColoredViewProps>(props);
[super updateProps:props oldProps:oldProps];
if (!newViewProps.color.empty()) {
NSString *hexString = [NSString stringWithUTF8String:newViewProps.color.c_str()];
self.backgroundColor = [self rgbaFromHexString:hexString];
}
self.layer.cornerRadius = newViewProps.cornerRadius;
}
关键点:
componentDescriptorProvider是 Fabric 注册组件的入口updateProps:oldProps:是属性更新的生命周期方法,使用 C++shared_ptr传递- 通过
std::static_pointer_cast把泛型 Props 转为具体类型
3.3 TurboModule 实现 --- CounterTurboModule.mm
TurboModule 的 iOS 实现需要同时遵循 Codegen 生成的协议和实现 JSI 桥:
objc
#import "CounterTurboModule.h"
@implementation CounterTurboModule {
double _count;
}
RCT_EXPORT_MODULE(NativeCounter)
- (void)getValue:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
resolve(@(_count));
}
- (void)increment:(double)step resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
_count += step;
resolve(@(_count));
}
- (void)decrement:(double)step resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
_count -= step;
resolve(@(_count));
}
- (void)reset:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
_count = 0;
resolve(nil);
}
- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params {
return std::make_shared<NativeCounterSpecJSI>(params);
}
关键点:
RCT_EXPORT_MODULE(NativeCounter)注册模块,名称必须与 JS Spec 中的名称一致- 方法签名与 Codegen 生成的协议完全对应
getTurboModule:返回std::shared_ptr<TurboModule>,这是新架构与传统 Bridge 的本质区别------直接 C++ 对象调用,无 JSON 序列化开销
3.4 Codegen 生成的头文件结构
执行 Codegen 后,MyRNAppSpecs.h 会自动生成:
objc
@protocol NativeCounterSpec <RCTBridgeModule, RCTTurboModule>
- (void)getValue:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)increment:(double)step resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject;
// ...
@end
namespace facebook::react {
class JSI_EXPORT NativeCounterSpecJSI : public ObjCTurboModule {
public:
NativeCounterSpecJSI(const ObjCTurboModule::InitParams ¶ms);
};
}
协议 + JSI 类 共同构成了 TurboModule 的 C++ 通信层。
第四步:Android 原生实现(Kotlin)
4.1 Fabric Component --- ColoredViewManager.kt
kotlin
class ColoredViewManager : BaseViewManager<View, LayoutShadowNode>() {
override fun getName(): String = "NativeColoredView"
override fun createViewInstance(context: ThemedReactContext): View = View(context)
@ReactProp(name = "color", customType = "Color")
fun setColor(view: View, color: Int?) {
if (color != null) view.setBackgroundColor(color)
}
@ReactProp(name = "cornerRadius", defaultFloat = 0f)
fun setCornerRadius(view: View, radius: Float) {
view.clipToOutline = true
view.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, radius)
}
}
}
}
4.2 TurboModule --- CounterTurboModule.kt
kotlin
class CounterTurboModule(reactContext: ReactApplicationContext)
: NativeCounterSpec(reactContext) {
private var count: Double = 0.0
override fun getValue(promise: Promise) { promise.resolve(count) }
override fun increment(step: Double, promise: Promise) {
count += step; promise.resolve(count)
}
override fun decrement(step: Double, promise: Promise) {
count -= step; promise.resolve(count)
}
override fun reset(promise: Promise) { count = 0.0; promise.resolve(null) }
}
Android 侧直接继承 Codegen 生成的
NativeCounterSpec抽象类,强制实现所有方法,类型安全由编译期保证。
4.3 注册入口 --- MyRNPackage.kt
kotlin
class MyRNPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(CounterTurboModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(ColoredViewManager())
}
}
在 MainApplication.kt 中注入:
kotlin
PackageList(this).packages.apply {
add(MyRNPackage())
}
第五步:iOS 原生容器嵌入(重头戏)
许多团队的需求是 在已有 iOS App 中嵌入 RN 页面 ,而非纯 RN 工程启动。本项目的 IOSRNContainer 正是这种场景的完整方案。
5.1 容器工程的 Podfile 配置
区别于纯 RN 工程,容器工程的 Podfile 需要指向外部 RN 工程的路径并注入 Codegen 头文件:
ruby
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
rn_app_path = File.expand_path('../MyRNModule', __dir__)
rn_node_modules_path = File.join(rn_app_path, 'node_modules')
rn_react_native_path = File.join(rn_node_modules_path, 'react-native')
target 'IOSRNContainer' do
use_react_native!(
:path => rn_react_native_path,
:app_path => rn_app_path
)
post_install do |installer|
react_native_post_install(installer, rn_react_native_path, :mac_catalyst_enabled => false)
# 🔑 关键:手动注入 Codegen header 搜索路径
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.user_project.targets.each do |target|
next unless target.name == 'IOSRNContainer'
target.build_configurations.each do |config|
paths = config.build_settings['HEADER_SEARCH_PATHS'] || ['$(inherited)']
codegen_path = '${PODS_ROOT}/Headers/Public/ReactCodegen'
unless paths.include?(codegen_path)
paths << codegen_path
end
config.build_settings['HEADER_SEARCH_PATHS'] = paths
end
end
end
end
end
⚠️ 踩坑提示 :容器工程默认不会自动引入 Codegen 生成的头文件搜索路径,必须在
post_install中手动注入${PODS_ROOT}/Headers/Public/ReactCodegen,否则#import <MyRNAppSpecs/MyRNAppSpecs.h>会报找不到文件。
5.2 单例 RN 工厂 --- ReactNativeHost.swift
swift
final class ReactNativeHost {
static let shared = ReactNativeHost()
private var reactNativeFactory: RCTReactNativeFactory?
func bootstrap(with launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) {
guard reactNativeFactory == nil else { return }
let delegate = ContainerReactNativeDelegate()
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeFactory = RCTReactNativeFactory(delegate: delegate)
}
func makeRootView(moduleName: String, initialProperties: [String: Any]? = nil) -> UIView {
bootstrap()
return reactNativeFactory!.rootViewFactory.view(
withModuleName: moduleName,
initialProperties: initialProperties,
launchOptions: nil
)
}
}
设计思路 :单例模式管理
RCTReactNativeFactory,避免重复初始化。makeRootView每次创建新的 RN 根视图。
5.3 自定义 Delegate --- Metro 热加载
swift
final class ContainerReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func bundleURL() -> URL? {
#if DEBUG
// 支持 Info.plist 配置自定义 Metro IP
if let customIP = Bundle.main.object(forInfoDictionaryKey: "RNMetroServerIP") as? String {
return URL(string: "http://\(customIP)/index.bundle?platform=ios&dev=true&minify=false")
}
return RCTBundleURLProvider.sharedSettings()
.jsBundleURL(forBundleRoot: "index")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
5.4 包装 ViewController
swift
final class ReactNativeViewController: UIViewController {
override func loadView() {
view = ReactNativeHost.shared.makeRootView(
moduleName: "MyRNModule",
initialProperties: ["fromNative": true, "entry": "IOSRNContainer"]
)
}
}
moduleName必须与AppRegistry.registerComponent(appName, ...)中的名称一致。
5.5 AppDelegate 启动
swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: ...) -> Bool {
ReactNativeHost.shared.bootstrap(with: launchOptions)
return true
}
}
在 AppDelegate 的 didFinishLaunchingWithOptions 中完成 RN 环境初始化,之后任何页面都可以通过 ReactNativeHost.shared.makeRootView(...) 创建 RN 视图。
第六步:JS 层消费
在 App.tsx 中安全地引用原生模块:
typescript
let NativeCounter: any = null;
let NativeColoredView: any = null;
try {
NativeCounter = require('./specs/NativeCounter').default;
} catch (_) {}
try {
NativeColoredView = require('./specs/NativeColoredView').default;
} catch (_) {}
当原生模块未注册时(例如在 Expo Go 中运行),try-catch 能让页面优雅降级,显示"模块未注册"提示。
🔥 新架构架构图(数据流)
markdown
┌──────────────────────────────────────────┐
│ JavaScript │
│ NativeCounter TS Spec ──► TurboModule │
│ NativeColoredView TS Spec ──► Fabric │
└─────────────────┬────────────────────────┘
│ Codegen
▼
┌──────────────────────────────────────────┐
│ C++ 胶水代码 (JSI) │
│ NativeCounterSpecJSI : ObjCTurboModule │
│ NativeColoredViewComponentDescriptor │
└──────────────┬──────────────┬────────────┘
│ │
┌─────────▼────┐ ┌─────▼──────────┐
│ iOS (ObjC++)│ │ Android (Kotlin)│
│ CounterTM.mm │ │ CounterTM.kt │
│ ColoredView.mm│ │ ColoredView.kt │
└──────────────┘ └────────────────┘
对比旧架构:
- ❌ 旧架构:JS → JSON → Bridge → Native(序列化/反序列化开销大)
- ✅ 新架构:JS → JSI (C++ 虚表调用) → Native(零序列化,直接调用)
📊 核心知识点总结
| 概念 | 旧架构 | 新架构 |
|---|---|---|
| 通信机制 | Bridge (JSON) | JSI (C++ 直接调用) |
| 原生模块 | NativeModules | TurboModule |
| UI 渲染 | UIManager (Shadow Thread) | Fabric (同步渲染) |
| 接口定义 | 手动同步 | Codegen 自动生成 |
| 线程模型 | 三线程(JS/Shadow/Main) | 多线程并发 |
🎯 本项目的意义
这套代码不是玩具 Demo,它涵盖了企业级 RN 新架构落地的所有核心环节:
- JS Spec 类型定义 → Codegen 自动生成
- iOS ObjC++ 实现 → C++ Props 直接通信
- Android Kotlin 实现 → 继承 Codegen 基类
- iOS 原生容器嵌入 → 单例工厂 + 自定义 Delegate
- Podfile 配置 → Codegen 头文件路径手动注入
- 优雅降级 → try-catch 处理模块未注册
你可以直接 Clone、运行、改造,作为团队内部技术预研或面试作品都很有说服力。
项目地址 :
https://github.com/maochengfangv/21DaysALLIN/tree/main运行方式:
bash# 1. MyRNModule (纯 RN 工程) cd MyRNModule && yarn && cd ios && pod install && cd .. && yarn ios # 2. IOSRNContainer (原生 iOS 容器) cd IOSRNContainer && pod install && open IOSRNContainer.xcworkspace
如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注,我会持续输出 React Native 新架构的实战系列文章。