普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月15日首页

RN 的新模块系统 Turbo module

作者 Joyee691
2026年4月15日 15:50

本文所有代码如果没有特别标注的话,默认用的都是 v0.76.0 的 RN 代码

TurboModule 是什么

要讲清楚这个问题,我们要先从 bridge 架构的 Native module 系统开始说起,旧架构的 Native module 系统的调用流程大概是这样的:

JS code
  │
  │   UIManager.dispatchViewManagerCommand(tag, commandID, params)
  ▼
+----------------------+
| JS UIManager proxy   |
| (generated function) |
+----------------------+
  │
  │   enqueueNativeCall(moduleID, methodID, args)
  ▼
+----------------------+
|   BatchedBridge      |
|   call queue         |
+----------------------+
  │
  │   batch + flush
  ▼
+----------------------+
|    Bridge payload    |
| [moduleIDs]          |
| [methodIDs]          |
| [params]             |
+----------------------+
  │
  │   pass to native
  ▼
+----------------------+
|  Native bridge side  |
+----------------------+
  │
  ├─ lookup module by moduleID
  ├─ lookup method by methodID
  └─ convert args
  ▼
+----------------------+
|  Native UIManager    |
+----------------------+
  │
  ├─ resolve reactTag
  ├─ resolve command
  └─ dispatch
  ▼
+----------------------+
| ViewManager / View   |
+----------------------+
  │
  ▼
Actual native UI effect

首先 JS 在调用的时候需要知道有哪些模块,以及对应模块的方法,这些资讯是由宿主侧在初始化阶段注入的

当 JS 调用 Native module 时,JS 还有一层代理(proxy)层将模块方法的调用统一转换成 enqueueNativeCall 方法的调用

当这个方法进入 JS 侧 bridge 的时候,会被打包、序列化后传送给宿主侧的 bridge 由这边的 bridge 进行反序列化、解析后调用对应的原生模块方法;如果这个原生模块的方法存在回调,还需要把这个流程反过来一次,然后找到对应的 callID(JS 调用原生模块方法的编号)拿到对应的 callback 执行

这种调用模式会有什么问题呢?我们用一个表格来看看:

旧 Native Module 的架构问题 问题描述 TurboModule 怎么修复
以 Bridge 为中心 模块系统依赖跨桥消息模型,能力受限(强制异步、对高频/小细粒度调度不友好等等) 改为 JSI binding 为中心,使得模块可以像 JS runtime 中的本地对象能力被调用
接口契约松散 原生模块缺乏单一数据源约束,JS/Android/IOS 各自实现,三者依靠文档、约定、测试保持一致,长期容易造成平台不一致重构风险高团队协作成本上升等问题 用 spec 来约束类型 + Codegen 根据类型约束生成 C++/Android/IOS 侧脚手架文件强化契约,建立单一数据源
生命周期治理弱 旧原生模块系统虽然也支持懒加载,但它的生命周期依赖 bridge 上下文驱动,生命周期管理边界也十分分散 TurboModuleManager 统一按需创建和管理
支持两层缓存:模块实例、JS 方法属性按需加载
UI 模块和普通原生能力混杂 旧 Native Module 既承载普通能力模块,也承载像 UIManager 这类 UI 基础设施,导致 “能力模块” 和 “渲染/视图系统” 职责混杂,抽象层次不清晰 TurboModule 负责普通原生能力;Fabric 负责 UI/视图系统,从架构上完成职责拆分

基于这些区别,现在可以回答本章节标题的问题了:TurboModule system 是 RN 新架构中面向非 UI 原生能力的模块系统,它以 JS runtime 为中心,替代框架中以 bridge 为中心的 Native Module system;并通过更清晰的模块边界、按需创建和更强的接口契约支持 RN 架构长期演进

设计方案

新旧模块兼容方案

在进入架构设计前,我们需要先了解一些背景知识:

新的 TurboModule 不仅重写了整个 NativeModule 系统,还更改了之前的调用方式

在 Bridge 时代,NativeModule 的调用是这样子的:

import { NativeModules } from 'react-native';

NativeModules.Vibration.vibrate(500);

但是在 TurboModule 中,调用方式变成了:

import { TurboModuleRegistry } from 'react-native';

const NativeVibration = TurboModuleRegistry.getEnforcing('Vibration')
NativeVibration.vibrate(500)

个人猜测改变调用方式的动机有两个:

  1. 相比于之前分散的模块,新的方法按模块名从 TurboModule 系统取,更适合“按名称查找”和“按需解析”的新语义,且加强了原生模块之间的内聚性
  2. 考虑到原生模块的迁移成本,通过 TurboModuleRegistry 能更好的适配旧的原生模块调用方式,使得旧模块也能在新架构上继续使用

特别是第二点,RN 为了让新架构也能兼容旧写法的 Native module,设计了如下的兼容流程:

RN_legacy_vs_turboModule

说明一下:

  1. 当我们在新架构调用了 Vibration.vibrate(500) 方法后,会进入到对应的 spec 文件中
  2. spec 文件就是 typescript 编写的文件,它声明了原生模块的类型,并且会暴露一个 TurboModuleRegistry.getEnforcing('Vibration') 方法的返回值
  3. getEnforcing 方法中,会判断当前的模块是否为 TurboModule
  4. 【接步骤 3】如果是,则通过原生平台的 TurboModuleProvider 模块去查找对应实现(对应图中最右边的橘色正方形)
  5. 【接步骤 3】如果不是,则要先判断是否要启用兼容逻辑(判断依据是 useLegacyNativeModuleInterop 是否为 true,只有当前 RN 在 bridge 模式或者原生平台提供了 LegacyModuleProvider 才会返回 true)
  6. 【接步骤 5】如果不启用则直接返回 null,因为没有找到对应原生模块
  7. 【接步骤 5】如果启用了,就判断原生平台是否提供 LegacyModuleProvider,如果有则走 LegacyModuleProvider 的逻辑
  8. 【接步骤 7】如果没有就回退到 bridge 的 enqueueNativeCall 解决方案(对应到我们开篇的流程图)

至于 TurboModuleProviderLegacyModuleProvider 分别做了什么,以及 RN 是如何管理模块的生命周期,我们可以来看看下个章节的架构设计~

调用链路

由于 LegacyModuleProviderTurboModuleProvider 基本类似,区别在于 LegacyModuleProvider 需要兼容之前基于 Bridge 的原生模块,所以我们重点看一下新的 TurboModule 调用链路:

turboModule flow

TurboModule 的调用链路中总共有两个主要角色:Js runtime 以及 C++ 中的 TurboModuleBinding

看过本专栏 JSI 文章的读者应该知道,在新架构中的 XXXbinding 一般就是负责把各种能力挂到 global 对象上

整个调用链路分为两个部分:初始化阶段、调用阶段

在 RN 应用初始化过程中,会调用 TurboModuleBinding::install 方法,这个方法会做两件事:

  1. 往 global 挂 __turboModuleProxy 属性,这是一个 C++ 侧的方法,也是 JS 调用 turboModule 的入口
  2. 如果当前是 bridgeless 模式(也就是使用 JSI),会往 global 挂 nativeModuleProxy 属性,这也是一个 C++ 侧的方法,负责兼容老架构的原生模块

至此,初始化阶段就完成了,接下来进入调用阶段

调用的发起点是 JS 侧(对应图中左侧 JS runtime),总共分为两步:

  1. 获得 TurboModule 对应 JS 对象的引用(对应到图中左侧的 jsRepresentation,为了方便理解我们把它赋值给了 NativeVibration 这个更加语义化的变量)
  2. 从该引用取得方法然后调用

首先是第一步获取 TurboModule 对应 JS 对象的引用,我们可以通过 global.__turboModuleProxy 取得,这是一个可以通过 JSI 调用的 C++ 方法,最后会调用 TurboModuleBinding::getModule 方法,该方法会做四件事:

  1. 创建一个 JS 对象 jsRepresentation,用来存放后续所需 TurboModule 的实例
  2. 去宿主平台的 TurboModuleManager(IOSAndroid)取得对应 TurboModule 的实例
  3. 把 TurboModule 的实例放到 jsRepresentation.__proto__ 中,这是 TurboModule 系统实现模块按照方法/属性级别缓存的主要设计
  4. 最后,我们把 jsRepresentation 返回给 JS runtime,让 JS 持有这个对象的引用以便后续直接调用其中的方法

以上的步骤只有第一次的时候需要,后续由于 JS 已经持有了 jsRepresentation 的引用,所以 JS 直接通过这个对象访问所需方法即可~

源码解析

在这个小节,我们来看看上个小节的初始化以及调用阶段的具体代码实现

首先是初始化阶段,我们先来看看 TurboModuleBinding::install 是怎么实现的:

// in packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp

// 这个方法会在应用初始化的时候被宿主平台各自实现的 TurboModuleManager 调用
// 目的是为了在 global 对象上挂载原生模块调用所需方法
void TurboModuleBinding::install(
    jsi::Runtime &runtime, TurboModuleProviderFunctionType &&moduleProvider,
    TurboModuleProviderFunctionType &&legacyModuleProvider,
    std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection) {
  // 挂载 __turboModuleProxy
  runtime.global().setProperty(
      runtime, "__turboModuleProxy",
    // 这是一个 C++ 方法
      jsi::Function::createFromHostFunction(
          runtime, jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"), 1,
        // 传入了一个 lymbda,这个 lymbda 会在 JS 访问 global.__turboModuleProxy 时被调用
          [binding = TurboModuleBinding(runtime, std::move(moduleProvider),
                                        longLivedObjectCollection)](
              jsi::Runtime &rt, const jsi::Value &thisVal,
              const jsi::Value *args, size_t count) {
            // 检查参数的代码
            if (count < 1) {
              throw std::invalid_argument(
                  "__turboModuleProxy must be called with at least 1 argument");
            }
            std::string moduleName = args[0].getString(rt).utf8(rt);
            // 真正做事的方法
            return binding.getModule(rt, moduleName);
          }));

  // 因为 0.76.0 版本还需要兼容之前的 bridge 模式,所以会用 RN$Bridgeless 来标识现在用的是 JSI 架构的代码
  if (runtime.global().hasProperty(runtime, "RN$Bridgeless")) {
    bool rnTurboInterop = legacyModuleProvider != nullptr;
    auto turboModuleBinding =
        legacyModuleProvider ? std::make_unique<TurboModuleBinding>(
                                   runtime, std::move(legacyModuleProvider),
                                   longLivedObjectCollection)
                             : nullptr;
    auto nativeModuleProxy = std::make_shared<BridgelessNativeModuleProxy>(
        std::move(turboModuleBinding));
    defineReadOnlyGlobal(runtime, "RN$TurboInterop",
                         jsi::Value(rnTurboInterop));
    // 主要代码,目的是挂载 nativeModuleProxy 到 global 对象上
    defineReadOnlyGlobal(
        runtime, "nativeModuleProxy",
        jsi::Object::createFromHostObject(runtime, nativeModuleProxy));
  }
}

接下来我们来看看 getModule 做了什么:

// in packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp

// 获得具体模块实现的方法,目标是最后返回一个带着模块实现的 JS 对象 jsRepresentation
jsi::Value TurboModuleBinding::getModule(jsi::Runtime &runtime,
                                         const std::string &moduleName) const {
  std::shared_ptr<TurboModule> module;
  {
    SystraceSection s("TurboModuleBinding::moduleProvider", "module",
                      moduleName);
// 调用双端实现的 TurboModuleProvider 来获取对应的模块实例
    module = moduleProvider_(moduleName);
  }
  if (module) {
    // 这里是第一层缓存
    // 如果这不是第一次获取该模块实例,我们可以从模块中找到上一次返回的 jsRepresentation 
    // 这里直接返回即可,无需再创建一次 jsRepresentation 对象
    auto &weakJsRepresentation = module->jsRepresentation_;
    if (weakJsRepresentation) {
      auto jsRepresentation = weakJsRepresentation->lock(runtime);
      if (!jsRepresentation.isUndefined()) {
        return jsRepresentation;
      }
    }

    // 如果是第一次获取该模块实例,我们就需要先创建一个空的 jsRepresentation 对象
    jsi::Object jsRepresentation(runtime);
    weakJsRepresentation =
        std::make_unique<jsi::WeakObject>(runtime, jsRepresentation);

    // ⚠️ 核心代码
    // jsRepresentation 解决的是 “实例属性的缓存问题”
    // 具体做法是:
    // 1. 第一次创建并返回的 jsRepresentation 是一个空对象,但是我们用原型链将其与模块实例关联起来
    // 2. 如果我们第一次调用了该模块的方法,会因为当前对象为空而去原型链找,于是就进入了模块原型的 get 方法
    // 3. 模块的 get 方法被触发会把对应的属性缓存在 jsRepresentation 的属性中
    // 4. 这样一来,后面如果我们再次调用同样的方法,就可以直接在 jsRepresentation 的属性中查找到,不需要再进行原型链查找了
    // 具体的实现等我们聊到 TurboModuleProvider 的实现会再进行分析
    auto hostObject =
        jsi::Object::createFromHostObject(runtime, std::move(module));
    jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject));

    // 把刚刚创建的对象返回给 JS
    return jsRepresentation;
  } else {
    // 如果找不到对应的模块,直接返回 null
    return jsi::Value::null();
  }
}

接下来,我们来看看神秘的 TurboModuleProvider 都做了什么~

TurboModuleProvider 是由平台自行实现,所以会有两套实现,我们先来看看 IOS 的实现:

// in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

// 这个方法是 IOS 侧把 TurboModule 系统安装进 JS runtime 的入口
// 在这里 IOS 提供了 turboModuleProvider 以及 legacyModuleProvider 并在最后调用了上述 TurboModuleBinding::install 方法
- (void)installJSBindings:(facebook::jsi::Runtime &)runtime
{
  // 创建 turboModuleProvider,可以看到这是一个 lymbda
  // 当 getModule 需要查找模块的时候就会调用这个 lymbda
  // 留意这里的返回值是一个 TurboModule,这个跟后续的 legacyModuleProvider 是一致的
  // 也就是说,新旧原生模块的差异在这里被抹平了
  auto turboModuleProvider = [self,
                              runtime = &runtime](const std::string &name) -> std::shared_ptr<react::TurboModule> {
    auto moduleName = name.c_str();
// 性能埋点
    TurboModulePerfLogger::moduleJSRequireBeginningStart(moduleName);
    // 判断当前模块是否初始化了,如果没有的话也要埋个性能埋点
    // 这里只记录 objc 实现的原生模块,因为 objc 原生模块的初始化过程比较复杂,这个我们后面会聊
    auto moduleWasNotInitialized = ![self moduleIsInitialized:moduleName];
    if (moduleWasNotInitialized) {
      [self->_bridge.performanceLogger markStartForTag:RCTPLTurboModuleSetup];
    }

    // 关键代码,真正获取模块的逻辑
    // 如果该模块已经初始化了会直接返回实例,否则会执行初始化流程
    auto turboModule = [self provideTurboModule:moduleName runtime:runtime];

    // 初始化性能埋点
    if (moduleWasNotInitialized && [self moduleIsInitialized:moduleName]) {
      [self->_bridge.performanceLogger markStopForTag:RCTPLTurboModuleSetup];
    }

    // 埋点,记录是否获取成功
    if (turboModule) {
      TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName);
    } else {
      TurboModulePerfLogger::moduleJSRequireEndingFail(moduleName);
    }
    // 返回一个模块实例
    return turboModule;
  };

  // 一个开关,如果在 bridgeless 模式下默认是开启的
  // 用来开启兼容旧模块的开关,在开启的情况下才会提供 legacyModuleProvider
  if (RCTTurboModuleInteropEnabled()) {
    // 这里的代码跟上面 turboModuleProvider 基本一致,区别只在于获取模块的方法变了
    // 这里变成了 provideLegacyModule
    auto legacyModuleProvider = [self](const std::string &name) -> std::shared_ptr<react::TurboModule> {
      auto moduleName = name.c_str();
      
      TurboModulePerfLogger::moduleJSRequireBeginningStart(moduleName);

      // 关键代码
      auto turboModule = [self provideLegacyModule:moduleName];

      if (turboModule) {
        TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName);
      } else {
        TurboModulePerfLogger::moduleJSRequireEndingFail(moduleName);
      }
      return turboModule;
    };

    // 调用带有 legacyModuleProvide 的 TurboModuleBinding::install
    TurboModuleBinding::install(runtime, std::move(turboModuleProvider), std::move(legacyModuleProvider));
  } else {
    // 调用只有 turboModuleProvider 的 TurboModuleBinding::install
    TurboModuleBinding::install(runtime, std::move(turboModuleProvider));
  }
}

接下来我们就要进入 provideTurboModule 的具体实现(由于 provideLegacyModule 方法逻辑类似,这里就不额外说明了)

不过在看代码之前,我们需要先了解一些背景知识:TurboModule 内部实现到底有几种

  1. 纯 C++ 实现的 TurboModule:最 “纯” 的新架构模块,这也是最接近新架构设计的原生模块

  2. 全局导出的 C++ TurboModule:这个跟第一类其实一样,唯一的区别在于它俩的查找方式不同,上一个是通过 delegate 查找、它是靠 register 查找

  3. Objc 平台模块:这一类泛指所有基于 objc 实现的平台模块,细分下去可以分为三类:

    1. ObjC 实现的 TurboModule:这个指的是由 Objc 实现且实现了 getTurboModule 方法的模块
    2. legacy ObjC NativeModule:这个由 provideLegacyModule 负责处理
    3. RCTCxxModule:这个是在旧架构中使用 C++ 实现的模块,因为在旧架构中 C++ 模块需要 Objc 来中转,所以它在之前也被当成了 Objc 的模块

而 provideTurboModule 查找的顺序也是依据上面的顺序来的,下面我们看看具体代码:

// in packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm

/**
 * provideTurboModule 方法接受一个模块名作为参数,然后查找并返回返回对应的 TurboModule 模块实例
 * 查找流程如下:
 * 1. 查 _turboModuleCache
 * 2. 查 delegate 提供的 pure C++ TurboModule
 * 3. 查 global exported C++ TurboModule map
 * 4. 再查 ObjC module
**/
- (std::shared_ptr<TurboModule>)provideTurboModule:(const char *)moduleName runtime:(jsi::Runtime *)runtime
{
  /**
   * 第一步:如果这个模块已经创建过有缓存了,直接返回缓存
   * _turboModuleCache 是一个 unordered_map,保存 “模块名” 到 “模块实例指针” 的映射
   */
  auto turboModuleLookup = _turboModuleCache.find(moduleName);
  if (turboModuleLookup != _turboModuleCache.end()) {
    TurboModulePerfLogger::moduleJSRequireBeginningCacheHit(moduleName);
    TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName);
    return turboModuleLookup->second;
  }

  TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName);

  /**
   * 第二步:检查纯 C++ 模块(C++ 模块拥有最高优先级)
   */
  if ([_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
    int32_t moduleId = getUniqueId();
    TurboModulePerfLogger::moduleCreateStart(moduleName, moduleId);
    // 往 delegate 上找模块的实例
    auto turboModule = [_delegate getTurboModule:moduleName jsInvoker:_jsInvoker];
    if (turboModule != nullptr) {
      // 如果找到了就保存到缓存中
      _turboModuleCache.insert({moduleName, turboModule});
      TurboModulePerfLogger::moduleCreateEnd(moduleName, moduleId);
      // 然后返回实例
      return turboModule;
    }

    TurboModulePerfLogger::moduleCreateFail(moduleName, moduleId);
  }

  /**
   * 第三步:检查全局导出的 C++ 模块
   */
  auto &cxxTurboModuleMapProvider = globalExportedCxxTurboModuleMap();
  auto it = cxxTurboModuleMapProvider.find(moduleName);
  if (it != cxxTurboModuleMapProvider.end()) {
    auto turboModule = it->second(_jsInvoker);
    _turboModuleCache.insert({moduleName, turboModule});
    return turboModule;
  }

  /**
   * 第四步:找平台相关的模块,在 IOS 中就是 objc 模块
   */
  // 只有当 TurboModuleInterop 关闭或者当前模块是 TurboModule 的时候才会调用 _provideObjCModule 方法查找
  // legacyModule 不在这里处理,而是直接交给了 legacyModuleProvider
  // _provideObjCModule 会找到对应的模块,并且返回模块实例
  id<RCTBridgeModule> module =
      !RCTTurboModuleInteropEnabled() || [self _isTurboModule:moduleName] ? [self _provideObjCModule:moduleName] : nil;

  TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName);

  // 如果没有找到直接返回空指针
  if (!module) {
    return nullptr;
  }

  // 从模块实例找到对应的类
  Class moduleClass = [module class];

  // 找到模块需要的 queue
  dispatch_queue_t methodQueue = (dispatch_queue_t)objc_getAssociatedObject(module, &kAssociatedMethodQueueKey);
  if (methodQueue == nil) {
    RCTLogError(@"TurboModule \"%@\" was not associated with a method queue.", moduleClass);
  }

  // 根据 queue 创建 nativeMethodCallInvoker
  std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker =
      std::make_shared<ModuleNativeMethodCallInvoker>(methodQueue);

  // 在 bridgeless 模式下没有 bridge,所以忽略
  // 这个方法主要是把 TurboModule 的 native method invoker 交给 RCTCxxBridge 包装一下,让 bridge 能感知 TurboModule 的异步 native 调用,从而维持 onBatchComplete 等旧 bridge 行为
  if ([_bridge respondsToSelector:@selector(decorateNativeMethodCallInvoker:)]) {
    nativeMethodCallInvoker = [_bridge decorateNativeMethodCallInvoker:nativeMethodCallInvoker];
  }

  // 处理 RCTCxxModule
  if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) {
    // 直接用一个 TurboCxxModule 类包起来完事
    auto turboModule = std::make_shared<TurboCxxModule>([((RCTCxxModule *)module) createModule], _jsInvoker);
    // 还是一样放入 cache 中
    _turboModuleCache.insert({moduleName, turboModule});
    return turboModule;
  }

  // 最后我们来处理有 getTurboModul 方法的 objc 模块
  if ([module respondsToSelector:@selector(getTurboModule:)]) {
    ObjCTurboModule::InitParams params = {
        .moduleName = moduleName,
        .instance = module,
        .jsInvoker = _jsInvoker,
        .nativeMethodCallInvoker = nativeMethodCallInvoker,
        .isSyncModule = methodQueue == RCTJSThread,
        .shouldVoidMethodsExecuteSync = (bool)RCTTurboModuleSyncVoidMethodsEnabled(),
    };

    auto turboModule = [(id<RCTTurboModule>)module getTurboModule:params];
    if (turboModule == nullptr) {
      RCTLogError(@"TurboModule \"%@\"'s getTurboModule: method returned nil.", moduleClass);
    }
    _turboModuleCache.insert({moduleName, turboModule});

    if ([module respondsToSelector:@selector(installJSIBindingsWithRuntime:)]) {
      [(id<RCTTurboModuleWithJSIBindings>)module installJSIBindingsWithRuntime:*runtime];
    }
    return turboModule;
  }

  return nullptr;
}

总结一下,IOS 这块代码主要就是根据不同的模块类型一一处理,并且最后统一包裹成 TurboModule 返回

其中 _turboModuleCache 是一个关键的缓存机制,它保证了模块最多只会初始化一次

RN 团队在注释中有写了一个 TODO 想要把模块的生命周期管理下放到由模块自己管理(就是让 _turboModuleCache 不要像现在长时间保存实例缓存),但是我看了下到目前最新的 0.85 版本这个 TODO 还在

接下来我们来看看 Android 的 TurboModuleProvider 做了啥吧~

// in packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp

void TurboModuleManager::installJSIBindings(
    jni::alias_ref<jhybridobject> javaPart,
    bool shouldCreateLegacyModules) {
  auto cxxPart = javaPart->cthis();
  if (cxxPart == nullptr || !cxxPart->jsCallInvoker_) {
    return; 
  }

  // 从 runtimeExecutor 拿到 js runtime
  cxxPart->runtimeExecutor_([cxxPart,
                             javaPart = jni::make_global(javaPart),
                             shouldCreateLegacyModules](jsi::Runtime& runtime) {
    // 跟 IOS 一样,也是在这里调用了 install 方法
    TurboModuleBinding::install(
        runtime,
      // TurboModuleProvider
        cxxPart->createTurboModuleProvider(javaPart, &runtime),
        shouldCreateLegacyModules
      // LegacyModuleProvider
            ? cxxPart->createLegacyModuleProvider(javaPart)
            : nullptr);
  });
}

接下来我们看看具体实现:

// in packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp

TurboModuleProviderFunctionType TurboModuleManager::createTurboModuleProvider(
    jni::alias_ref<jhybridobject> javaPart,
    jsi::Runtime* runtime) {
  return [runtime, weakJavaPart = jni::make_weak(javaPart)](
             const std::string& name) -> std::shared_ptr<TurboModule> {
    auto javaPart = weakJavaPart.lockLocal();
    if (!javaPart) {
      return nullptr;
    }

    auto cxxPart = javaPart->cthis();
    if (cxxPart == nullptr) {
      return nullptr;
    }
// 具体实现在这~下面有代码~
    return cxxPart->getTurboModule(javaPart, name, *runtime);
  };
}

// 这个方法对应 IOS 的 provideTurboModule 方法
std::shared_ptr<TurboModule> TurboModuleManager::getTurboModule(
    jni::alias_ref<jhybridobject> javaPart,
    const std::string& name,
    jsi::Runtime& runtime) {
  const char* moduleName = name.c_str();
  TurboModulePerfLogger::moduleJSRequireBeginningStart(moduleName);

  /**
   * 第一步:如果这个模块已经创建过有缓存了,直接返回缓存
   */
  auto turboModuleLookup = turboModuleCache_.find(name);
  if (turboModuleLookup != turboModuleCache_.end()) {
    TurboModulePerfLogger::moduleJSRequireBeginningCacheHit(moduleName);
    TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName);
    return turboModuleLookup->second;
  }

  TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName);

  /**
   * 第二步:检查纯 C++ 模块(C++ 模块拥有最高优先级)
   */
  auto cxxDelegate = delegate_->cthis();
  auto cxxModule = cxxDelegate->getTurboModule(name, jsCallInvoker_);
  if (cxxModule) {
    turboModuleCache_.insert({name, cxxModule});
    return cxxModule;
  }

  /**
   * 第三步:检查全局导出的 C++ 模块
   */
  auto& cxxTurboModuleMapProvider = globalExportedCxxTurboModuleMap();
  auto it = cxxTurboModuleMapProvider.find(name);
  if (it != cxxTurboModuleMapProvider.end()) {
    auto turboModule = it->second(jsCallInvoker_);
    turboModuleCache_.insert({name, turboModule});
    return turboModule;
  }

  /**
   * 第四步:找平台相关的模块,在 Android 中就是 java 模块
   */
  static auto getTurboJavaModule =
      javaPart->getClass()
          ->getMethod<jni::alias_ref<JTurboModule>(const std::string&)>(
              "getTurboJavaModule");
  auto moduleInstance = getTurboJavaModule(javaPart.get(), name);
  if (moduleInstance) {
    TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName);
    JavaTurboModule::InitParams params = {
        .moduleName = name,
        .instance = moduleInstance,
        .jsInvoker = jsCallInvoker_,
        .nativeMethodCallInvoker = nativeMethodCallInvoker_};

    auto turboModule = cxxDelegate->getTurboModule(name, params);
    if (moduleInstance->isInstanceOf(
            JTurboModuleWithJSIBindings::javaClassStatic())) {
      static auto getBindingsInstaller =
          JTurboModuleWithJSIBindings::javaClassStatic()
              ->getMethod<BindingsInstallerHolder::javaobject()>(
                  "getBindingsInstaller");
      auto installer = getBindingsInstaller(moduleInstance);
      if (installer) {
        installer->cthis()->installBindings(runtime);
      }
    }

    turboModuleCache_.insert({name, turboModule});
    TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName);
    return turboModule;
  }

  // 处理旧架构中使用 C++ 实现的模块(对应 IOS 的 RCTCxxModule)
  static auto getTurboLegacyCxxModule =
      javaPart->getClass()
          ->getMethod<jni::alias_ref<CxxModuleWrapper::javaobject>(
              const std::string&)>("getTurboLegacyCxxModule");
  auto legacyCxxModule = getTurboLegacyCxxModule(javaPart.get(), name);
  if (legacyCxxModule) {
    TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName);

    auto turboModule = std::make_shared<react::TurboCxxModule>(
        legacyCxxModule->cthis()->getModule(), jsCallInvoker_);
    turboModuleCache_.insert({name, turboModule});

    TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName);
    return turboModule;
  }

  return nullptr;
}

可以看到 Android 的实现跟 IOS 基本没区别

在这个小节的最后,我们来聊一下 TurboModule 的两层缓存是怎么实现的:

  1. 第一层缓存是在宿主的 turboModuleCache_ 中这里存放了所有被初始化了的原生模块实例
  2. 第二层缓存是在 C++ 的 jsRepresentation,这是一个 JS 对象,每个对象对应到一个模块实例,模块实例通过挂在它的原型链上使得其可以访问模块的方法

访问模块的核心方法在 TurboModule.h

// in packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h

// 在 TurboModuleBinding::getModule 方法中,我们知道它返回了 jsRepresentation 并且把对应模块实例挂上了它的原型链
// 如果在 JS 侧访问某个模块方法但是在 jsRepresentation 中找不到时,会试图往原型链上找,于是就会命中这里的 get 方法
facebook::jsi::Value get(
      facebook::jsi::Runtime& runtime,
      const facebook::jsi::PropNameID& propName) override {
    {
      // 在当前 TurboModule 实例上找到对应的属性
      auto prop = create(runtime, propName);
      
      // 对于访问过的实例,我们把它放进 jsRepresentation 的属性中,这样下次访问就不用在往原型链上走了
      if (jsRepresentation_ && !prop.isUndefined()) {
        jsRepresentation_->lock(runtime).asObject(runtime).setProperty(
            runtime, propName, prop);
      }
      return prop;
    }
  }

可以看到,这一个小小的 get 方法就完成了对模块属性级别的缓存

Codegen

在了解了 TurboModule 的设计方案以及调用链路后,我们接下来要补齐 TurboModule 的最后一块拼图:Codegen

根据 官网博客 的描述,codegen 是一个可选的工具,所以我们首先需要知道的是它的优势是什么,以及什么时候推荐使用

Codegen 的优势

Codegen 的优势可以一句话总结:它用一份强类型 JS/TS spec 生成 Android、iOS、C++ 原生层所需的接口与胶水代码,从而减少样板代码维护成本,并降低 JS 与 Native 之间类型不一致跨语言调用出错的风险

怎么理解这句话呢?

假设我们想要实现一个原生模块叫 NativeNotifier 它的职责是调用原生的能力,生成一个系统弹窗出来(示例见下图)

因为这个模块调用了 原生的能力 所以我决定用 objc 来实现,下面来讲一下如果不使用 codegen 的话,要怎么做:

第一步,先创建 JS wrapper:

// NativeNotifier.ts
import {TurboModuleRegistry} from 'react-native';

type NativeNotifierType = {
  show(message: string): void;
};

export default TurboModuleRegistry.getEnforcing<NativeNotifierType>(
  'NativeNotifier',
);

第二步,创建 IOS header:

// NativeNotifier.h
#import <React/RCTBridgeModule.h>

// 用 RCTBridgeModule 是为了让 RN 能发现这个 ObjC module,并读取 RCT_EXPORT_MODULE / RCT_EXPORT_METHOD 产生的 metadata
@interface NativeNotifier : NSObject <RCTBridgeModule>
@end

第三步,创建 iOS 实现:

// NativeNotifier.mm
#import "NativeNotifier.h"
#import <React/RCTUtils.h>
#import <ReactCommon/RCTInteropTurboModule.h>
#import <ReactCommon/RCTTurboModule.h>
#import <UIKit/UIKit.h>

using namespace facebook::react;
@interface NativeNotifier () <RCTTurboModule>
@end

@implementation NativeNotifier

// 暴露模块与方法名
RCT_EXPORT_MODULE(NativeNotifier)
RCT_EXPORT_METHOD(show:(NSString *)message)
{
  // 具体逻辑实现,这里省略
}

- (std::shared_ptr<TurboModule>)getTurboModule:
    (const ObjCTurboModule::InitParams &)params
{
  // 把这个 ObjC module 包装成 TurboModule
  return std::make_shared<ObjCInteropTurboModule>(params);
}

@end

第四步,为了让 xcode 编译 NativeNotifier.mm,我们需要把它加入到 Xcode target 中

第五步,在 App 中调用它:

import NativeNotifier from './NativeNotifier';

export default function App() {

  return (
    <SafeAreaView style={styles.container}>
      <Pressable
        style={({ pressed }) => [
          styles.button,
          pressed && styles.buttonPressed,
        ]}
        // 调用这个模块方法
        onPress={() => NativeNotifier.show('Hello from native iOS code')}>
        <Text style={styles.buttonText}>Show native message</Text>
      </Pressable>
    </SafeAreaView>
  );
}

如果我们纯手写的话,我们不仅要先知道 TurboModuleRegistry.getEnforcing 的用法,还需要知道 RCTBridgeModuleRCTTurboModuleObjCInteropTurboModule 的用法与区别,最关键的是,这个流程我们需要在 Android 再来一次

这还是初次开发这种简单模块的情况,如果后续需要迭代修改,或者是模块的复杂度上去了,维护成本巨大,双端的一致性也是个大问题

Codegen 是怎么解决的呢?

第一步,在项目根目录创建 spec 目录以及 JS wrapper

// specs/NativeNotifier.ts

// 这个文件既是 JS 调用入口,也是 Codegen 读取的 spec
// 文件目录名字可以通过在 package.json 自定义,但约定俗成使用 specs
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

// 很重要的类型声明,Codegen 会通过这个类型生成胶水代码
export interface Spec extends TurboModule {
  show(message: string): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeNotifier');

第二步,去 package.json 配置相关信息,具体配置参考 官方博客

{
  "codegenConfig": {
    "name": "DemoSpec",  // SpecName
    "type": "modules",  // types
    "jsSrcsDir": "specs",  // source_dir
    "android": {
      "javaPackageName": "com.demo"  // java.package.name
    },
}

第三步,创建 iOS 实现:

// ios/Demo/NativeNotifier.h
#import <Foundation/Foundation.h>

@interface NativeNotifier : NSObject
@end
// ios/Demo/NativeNotifier.mm

#import "NativeNotifier.h"
#import <React/RCTUtils.h>
#import <DemoSpec/DemoSpec.h>
#import <UIKit/UIKit.h>

@interface NativeNotifier () <NativeNotifierSpec>
@end

@implementation NativeNotifier

RCT_EXPORT_MODULE()

- (void)show:(NSString *)message
{
  // 具体逻辑实现,这里省略
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params
{
  return std::make_shared<facebook::react::NativeNotifierSpecJSI>(params);
}

@end

第四步,把 NativeNotifier.mm 加入到 Xcode target 中

第五步,实现 App.tsx 中的调用逻辑

完成了这五步后,在配置正确的情况下,IOS/Android build 流程会触发 Codegen,自动生成双端所需的接口和胶水代码;但具体的原生模块实现,例如 IOS 的 NativeNotifier.mm、Android 的 NativeNotifierModule.kt,仍然需要开发者自己实现并注册

细心的读者会发现,即使用了 Codegen,我们仍然需要:

  1. 创建 spec
  2. 配置 package.json
  3. 实现 iOS native 逻辑
  4. 加入 Xcode target
  5. 在 JS 中调用

也就是说,Codegen 并不是让创建 native module 的步骤变少,它真正减少的是隐藏在这些步骤背后的胶水代码:方法表、类型转换、JSI adapter、Android abstract spec、JNI glue,以及双端 API 签名同步的成本

简单画个比较表:

对比点 不使用 Codegen 使用 Codegen
JS/native API 来源 JS wrapper 和 native method 各写一份(总共三份) JS/TS spec 是唯一 API 来源(Single source of truth)
类型检查 主要靠人工保证 Codegen 根据 spec 生成 native 类型约束
iOS glue code 依赖 ObjCInteropTurboModule 运行时解析 RCT_EXPORT_METHOD metadata 生成 NativeNotifierSpec /NativeNotifierSpecJSI
Android glue code 需要手动写 module/package,签名一致性靠人工 生成 abstract Spec 和 JNI 胶水代码
方法名/参数数量错误 运行时更容易暴露 编译期或生成阶段更容易暴露
后续修改 API JS、iOS、Android 多处人工同步 先改 spec,再让生成代码约束 native 实现
适用场景 迁移旧 ObjC bridge module、快速兼容 新架构下新模块,更适合长期维护

总结

本文从 TurboModule 的设计开始,讲到了 Codegen 的优势以及为什么要使用 Codegen

诚然 Codegen 可以降低很多开发 TurboModule 的隐形成本,但作为开发者了解 TurboModule 以及背后的 JSI 才是主要的根基

毕竟只有掌握了底层的机制,才能往上搭出像 Nitro moduleExpo Modules API 这样的上层库/工具

❌
❌