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

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

  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 小时前
管谷歌的不是Pichai一个人,是一整层AI智能体
前端
米米商聊2 小时前
图标能点,按钮就可用了吗?键盘、名称与收藏状态
前端·javascript
Younglina2 小时前
三个晚上,我和 WorkBuddy 做了个生图网站
前端·后端·ai编程
xy34532 小时前
Axure 9.0 内联框架加载方式
前端·ui·html·axure·原型·产品设计
陈随易3 小时前
副业搞钱点子之收入查看器
前端·后端
Profile排查笔记3 小时前
浏览器指纹逆向怎么分析:从网络请求追踪到 JavaScript 采集链
前端·人工智能·后端·自动化
hudie7654 小时前
LikeShop 二开最常改的 5 个文件(附改前改后对比)
前端·uni-app·php·个人开发
qetfw4 小时前
Bash 函数与流程控制:read、if、for、case 验证
linux·前端·javascript·bash
鬼手点金5 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节
Shadow(⊙o⊙)5 小时前
LangChain 自定义工具全解析:从 @tool 到 bind_tools 的完整调用链路
java·服务器·前端·1024程序员节