中文资讯 掘金 AI Tue, 14 Jul 2026 正文 ok 1520 words

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

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

React Native 新架构实战:从零实现 TurboModule、Fabric 原生组件,并嵌入 iOS 原生容器,双端完整代码落地。
打开原文相关 2 · 机会 0
media3.2 KB media589 B media59.0 KB

中文

React Native 新架构实战:从零实现 TurboModule、Fabric 原生组件,并嵌入 iOS 原生容器,双端完整代码落地。

中文全文翻译正在补齐,可先查看下方英文原文。

English / 原文

🚀 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/                    
│   ├── specs/                     
│   │   ├── NativeCounter.ts       
│   │   └── NativeColoredView.ts   
│   ├── App.tsx                    
│   ├── package.json               
│   ├── ios/Podfile                
│   └── android/app/src/main/java/com/myrnmodule/
│       ├── CounterTurboModule.kt   
│       ├── ColoredViewManager.kt   
│       └── MyRNPackage.kt         
│
└── IOSRNContainer/                
    ├── IOSRNContainer/            
    │   ├── AppDelegate.swift      
    │   ├── ReactNativeHost.swift  
    │   ├── ReactNativeViewController.swift  
    │   └── ViewController.swift   
    ├── IOSRNModule/               
    │   ├── NativeColoredView.h/mm 
    │   ├── CounterTurboModule.h/mm
    │   └── CodegenHeaders/        
    └── Podfile                    

第一步: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"
      }
    }
  }
}

字段说明:

字段 含义
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 中:

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

在 AppDelegate 的 didFinishLaunchingWithOptions 中完成 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++ 直接调用)
原生模块 NativeModules TurboModule
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

运行方式 :

cd MyRNModule && yarn && cd ios && pod install && cd .. && yarn ios


cd IOSRNContainer && pod install && open IOSRNContainer.xcworkspace

如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注,我会持续输出 React Native 新架构的实战系列文章。