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

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

协议 + 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
  }
}

AppDelegatedidFinishLaunchingWithOptions 中完成 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 新架构落地的所有核心环节:

  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

运行方式

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

相关推荐
这是个栗子1 小时前
前端开发中的常用工具函数(八)
开发语言·前端·javascript
Hilaku1 小时前
Vue 和 React 真正的差距,不在语法,而在团队犯错成本
前端·javascript·程序员
研☆香2 小时前
为什么变量声明在赋值前也能使用?—— 深入理解 JavaScript 变量提升与作用域
开发语言·前端·javascript
Liora_Yvonne2 小时前
目录结构到底怎么设计?别再把 components 和 utils 当垃圾桶了
前端
光影少年2 小时前
RN 样式特点:Flex 布局默认、没有像素单位、样式不能级联
react native·react.js·掘金·金石计划
柯克七七2 小时前
我把 bug 修得太干净了,测试组以为这个模块本来就没问题
前端·javascript·typescript
Null1552 小时前
浏览器唤起桌面端应用(终极篇)
前端·javascript
Quz2 小时前
QML Label 完整用法与 Text 组件选型
前端·qt
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(三十四):后台初始化请求实现
前端·javascript·go