RN 0.86 新架构实战:TurboModule + Fabric 组件 + iOS 原生容器,完整代码开源

0 阅读7分钟

🚀 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)

📁 项目结构总览

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

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

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

{
  "codegenConfig": {
    "name": "MyRNAppSpecs",
    "type": "all",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.myrnmodule"
    },
    "ios": {
      "componentProvider": {
        "NativeColoredView": "NativeColoredView"
      },
      "modulesProvider": {
        "NativeCounter": "CounterTurboModule"
      }
    }
  }
}

字段说明:

字段含义
nameCodegen 输出的命名空间,iOS 为 #import <MyRNAppSpecs/MyRNAppSpecs.h>
type"all" 表示同时生成 TurboModule + Fabric Component 代码
jsSrcsDirTS Spec 文件所在的目录
ios.componentProviderFabric Component 名称 → 原生类名的映射
ios.modulesProviderTurboModule JS 名称 → 原生类名的映射

第三步:iOS 原生实现(ObjC++ + Fabric C++ API)

3.1 开启 New Architecture

MyRNModule/ios/Podfile 中:

ENV['RCT_NEW_ARCH_ENABLED'] = '1'

执行 pod install 后,Codegen 会在 Pods 中自动生成头文件 MyRNAppSpecs.h

3.2 Fabric Component 实现 — NativeColoredView.mm

Fabric Component 的核心是继承 RCTViewComponentView,并使用 C++ Props 对象来接收属性更新:

#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 桥:

#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 会自动生成:

@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 &params);
  };
}

协议 + JSI 类 共同构成了 TurboModule 的 C++ 通信层。


第四步:Android 原生实现(Kotlin)

4.1 Fabric Component — ColoredViewManager.kt

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

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

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 中注入:

PackageList(this).packages.apply {
    add(MyRNPackage())
}

第五步:iOS 原生容器嵌入(重头戏)

许多团队的需求是 在已有 iOS App 中嵌入 RN 页面,而非纯 RN 工程启动。本项目的 IOSRNContainer 正是这种场景的完整方案。

5.1 容器工程的 Podfile 配置

区别于纯 RN 工程,容器工程的 Podfile 需要指向外部 RN 工程的路径并注入 Codegen 头文件:

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

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 热加载

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

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 启动

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: ...) -> Bool {
    ReactNativeHost.shared.bootstrap(with: launchOptions)
    return true
  }
}

AppDelegatedidFinishLaunchingWithOptions 中完成 RN 环境初始化,之后任何页面都可以通过 ReactNativeHost.shared.makeRootView(...) 创建 RN 视图。


第六步:JS 层消费

App.tsx 中安全地引用原生模块:

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 能让页面优雅降级,显示"模块未注册"提示。


🔥 新架构架构图(数据流)

┌──────────────────────────────────────────┐
│                 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++ 直接调用)
原生模块NativeModulesTurboModule
UI 渲染UIManager (Shadow Thread)Fabric (同步渲染)
接口定义手动同步Codegen 自动生成
线程模型三线程(JS/Shadow/Main)多线程并发

🎯 本项目的意义

这套代码不是玩具 Demo,它涵盖了企业级 RN 新架构落地的所有核心环节:

  1. JS Spec 类型定义 → Codegen 自动生成
  2. iOS ObjC++ 实现 → C++ Props 直接通信
  3. Android Kotlin 实现 → 继承 Codegen 基类
  4. iOS 原生容器嵌入 → 单例工厂 + 自定义 Delegate
  5. Podfile 配置 → Codegen 头文件路径手动注入
  6. 优雅降级 → try-catch 处理模块未注册

你可以直接 Clone、运行、改造,作为团队内部技术预研或面试作品都很有说服力。


项目地址https://github.com/maochengfangv/21DaysALLIN/tree/main

运行方式

# 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 新架构的实战系列文章。