React Native 新架构原生模块开发指南

本文介绍如何基于 TurboModule(新架构原生模块)开发原生模块,内容基于 react-native-troikaopen in new window 仓库中的 packages/ 下的 overlay 等实现整理。

建议:以独立库的方式编写原生模块,便于复用与发布。

可使用 react-native-create-libopen in new window 一行命令生成库脚手架(支持新架构与 Monorepo),再按本文配置与实现业务逻辑。

本文中工程配置等章节均按独立库的目录与约定编写;若在主工程内直接开发,需按主工程路径与包名做对应调整。

前置条件

  • 项目已启用新架构(TurboModule)
  • 熟悉 React Native 与 TypeScript

一、TypeScript 规范(Codegen 入口)

规范文件命名约定:Native 为前缀(如 NativeOverlay.ts),Codegen 会据此生成各平台接口。注意:组件的规范以 NativeComponent 结尾,模块的规范以 Native 前缀区分

1.1 基本结构

// NativeOverlay.ts
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface OverlayOptions {
  overlayId: CodegenTypes.Int32;
  passThroughTouches?: boolean;
}

export interface Spec extends TurboModule {
  show(moduleName: string, options: OverlayOptions): void;
  hide(moduleName: string, overlayId: CodegenTypes.Int32): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('OverlayHost');
  • Spec 继承 TurboModule,定义模块的同步/异步方法事件
  • TurboModuleRegistry.getEnforcing<Spec>('OverlayHost')'OverlayHost'原生注册的模块名(Android getName()、iOS RCT_EXPORT_MODULE(OverlayHost)),需与两端一致。

1.2 方法定义

  • 无返回值或返回 Promise:视为异步方法,会生成 @ReactMethod(Android)且不阻塞 JS。
  • 有返回值且非 Promise:视为同步方法,会生成 @ReactMethod(isBlockingSynchronousMethod = true),调用会阻塞。

示例:

export interface Spec extends TurboModule {
  show(moduleName: string, options: OverlayOptions): void;
  hide(moduleName: string, overlayId: CodegenTypes.Int32): void;
  getCount(): CodegenTypes.Int32;
}

对应 Android 生成类似:

public abstract void show(String moduleName, ReadableMap options);
public abstract void hide(String moduleName, double overlayId);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getCount();

1.3 参数类型注意点

  • 数字:在 Android 上统一为 double(即便 TS 里写 CodegenTypes.Int32),原生侧如需整型需自行强转。

  • 对象:Android 为 ReadableMap,iOS 为 Codegen 生成的结构体(如 JS::NativeOverlay::OverlayOptions),不是 NSDictionary。若希望 iOS 也使用字典,可用 CodegenTypes.UnsafeObject

  • 回调:支持函数参数,例如 callback: (error: Error | null, value: number) => void。若用 Error,需自定义类型供 Codegen 识别:

    type Error = { code: string; message: string };
    

1.4 事件(EventEmitter)

若模块需要向 JS 发送事件(如“某 overlay 已显示”):

export type OnShownEvent = {
  overlayId: CodegenTypes.Int32;
};

export interface Spec extends TurboModule {
  readonly onShown: CodegenTypes.EventEmitter<OnShownEvent>;
}
  • JS 侧会得到 EventSubscription,需在适当时机调用 .remove() 取消订阅。
  • Android 会生成 emitOnShown(ReadableMap value),iOS 会生成 NativeOverlaySpecBaseemitOnShown:,在原生侧在合适时机调用即可。

1.5 封装层(推荐)

一般不直接暴露 TurboModule,而是包一层便于使用与换平台:

// index.ts
import NativeOverlay, { type OverlayOptions } from './NativeOverlay';

function show(moduleName: string, options: OverlayOptions) {
  NativeOverlay.show(moduleName, options);
}
function hide(moduleName: string, id: number) {
  NativeOverlay.hide(moduleName, id);
}
export const Overlay = { show, hide };

二、工程配置

2.1 package.json — Codegen

{
  "codegenConfig": {
    "name": "overlay",
    "type": "modules",
    "jsSrcsDir": "src",
    "android": {
      "javaPackageName": "com.reactnative.overlay"
    },
    "ios": {
      "modulesProvider": {
        "OverlayHost": "RNOverlayModule"
      }
    }
  }
}
  • name:库名(小写),用于生成 C++/ObjC 命名空间与头文件。
  • type"modules" 表示 TurboModule(不是 components)。
  • ios.modulesProviderRN 模块名 → iOS 类名(如 "OverlayHost"RNOverlayModule)。

2.2 react-native.config.js — Android 包实例

若 Android 的 Package 构造函数需要参数(如 ReactNativeHost),在此声明:

module.exports = {
  dependency: {
    platforms: {
      android: {
        packageInstance: 'new OverlayPackage(getReactNativeHost())',
      },
    },
  },
};

否则可省略或只配 android 的其它项。


三、Android 实现

3.1 build.gradle

与原生组件类似:apply plugin: 'com.facebook.react',并加入 codegen 生成目录:

apply plugin: 'com.android.library'
apply plugin: 'com.facebook.react'

android {
  namespace "com.reactnative.overlay"
  sourceSets {
    main {
      java.srcDirs += ["generated/java", "generated/jni"]
    }
  }
}
dependencies {
  api 'com.facebook.react:react-native:+'
}

3.2 运行 Codegen

在主工程 android 目录执行:

./gradlew generateCodegenArtifactsFromSchema

生成物在模块的 android/build/generated/source/codegen/(java + jni)。会得到 NativeOverlaySpec 等基类与 JSI 绑定。

3.3 实现模块类

继承 Codegen 生成的 Spec 基类,实现抽象方法并返回正确模块名:

public class OverlayModule extends NativeOverlaySpec implements LifecycleEventListener {

  private final ReactApplicationContext reactContext;
  private final ReactHost reactHost;

  public OverlayModule(ReactApplicationContext reactContext, ReactNativeHost reactNativeHost) {
    super(reactContext);
    this.reactContext = reactContext;
    this.reactHost = DefaultReactHost.getDefaultReactHost(reactContext, reactNativeHost, null);
    reactContext.addLifecycleEventListener(this);
  }

  @NonNull
  @Override
  public String getName() {
    return "OverlayHost";
  }

  @Override
  public void show(String moduleName, ReadableMap options) {
    UiThreadUtil.runOnUiThread(() -> {
      // 使用 options.getDouble("overlayId") 等实现 show 逻辑
    });
  }

  @Override
  public void hide(String moduleName, double overlayId) {
    UiThreadUtil.runOnUiThread(() -> {
      // 实现 hide 逻辑
    });
  }
}
  • 参数类型与 TS 一致:对象为 ReadableMap,数字在 Java 多为 double
  • 若需在主线程执行,使用 UiThreadUtil.runOnUiThread

3.4 事件发送

若 Spec 中定义了 EventEmitter,基类会提供 emitXxx(ReadableMap value)。在合适时机调用即可:

// 示例:发送 onShown
WritableMap payload = new JavaOnlyMap();
payload.putDouble("overlayId", id);
emitOnShown(payload);

3.5 生命周期

TurboModule 一般为单例,若需在模块“销毁”时释放资源,重写 invalidate(或按需 initialize):

@Override
public void invalidate() {
  reactContext.removeLifecycleEventListener(this);
  UiThreadUtil.runOnUiThread(this::handleDestroy);
}

private void handleDestroy() {
  // 释放资源、清理缓存等
}

3.6 注册

在自定义 ReactPackagecreateNativeModules 中返回模块实例;若使用 packageInstance,需传入对应参数(如 getReactNativeHost())。


四、iOS 实现

4.1 Podspec

与 Fabric 组件类似,使用 install_modules_dependencies(s)

Pod::Spec.new do |s|
  s.name         = "RNOverlay"
  s.version      = package["version"]
  s.source_files = "ios/**/*.{h,m,mm}"
  install_modules_dependencies(s)
end

4.2 运行 Codegen

在工程根目录执行:

bundle exec pod install

会生成 <overlay/overlay.h> 等头文件与 NativeOverlaySpecJSI 等实现。

4.3 头文件:实现 Spec 协议

  • 将实现文件从 .m 改为 .mm(C++ 生成代码)。
  • 声明实现 NativeOverlaySpec 协议;若有事件,可继承 Codegen 生成的 NativeOverlaySpecBase 以使用 emitOnShown: 等:
// RNOverlayModule.h
#import <Foundation/Foundation.h>
#import <overlay/overlay.h>
#import <React/RCTInitializing.h>

NS_ASSUME_NONNULL_BEGIN

@class RCTHost;

@interface RNOverlayModule : NSObject <NativeOverlaySpec, RCTInvalidating>

- (instancetype)initWithHost:(RCTHost *)host;

@end
NS_ASSUME_NONNULL_END

4.4 实现:getTurboModule + 方法

必须在实现文件中导出模块名并返回 TurboModule 的 C++ 包装(JSI):

// RNOverlayModule.mm
#import "RNOverlayModule.h"
#import "RNOverlay.h"
#import <React/RCTConversions.h>

RCT_EXPORT_MODULE(OverlayHost)

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

- (void)show:(NSString *)moduleName options:(JS::NativeOverlay::OverlayOptions &)options {
  // 使用 options.overlayId()、options.passThroughTouches() 等
}

- (void)hide:(NSString *)moduleName overlayId:(NSInteger)overlayId {
  // 实现 hide
}
  • 方法签名与 Codegen 生成的一致(如 options 为结构体引用)。
  • 主线程逻辑可用 dispatch_async(dispatch_get_main_queue(), ^{ ... });

4.5 事件发送

若继承 NativeOverlaySpecBase,在头文件中声明并实现,在适当时机调用:

[self emitOnShown:@{ @"overlayId": @(overlayId) }];

4.6 生命周期

实现 RCTInitializing / RCTInvalidating 并在实现文件中:

- (void)initialize {
  // 模块首次使用时调用
}

- (void)invalidate {
  // 释放资源、清理缓存
}

五、与旧架构(Bridge 模块)的区别

项目旧架构新架构(TurboModule)
规范无统一 TS 规范TS Spec + Codegen
Android继承 ReactContextBaseJavaModule + @ReactMethod继承 NativeXxxSpec,实现接口方法
iOS实现 RCTBridgeModule,手动导出方法实现 NativeXxxSpec + getTurboModule 返回 *SpecJSI
调用路径Bridge 异步序列化JSI 同步/异步,无序列化
类型运行时推断Codegen 生成类型,编译期检查

本仓库中 modal-edge-to-edgeEdgeToEdgeModule 仍为旧架构(ReactContextBaseJavaModule + @ReactMethod);overlay 为新架构 TurboModule,可对照两者差异。


六、小结

  1. TS 规范:建 NativeXxx.ts,Spec extends TurboModule,TurboModuleRegistry.getEnforcing<Spec>('ModuleName')。
  2. Codegen 配置:在 package.json 的 codegenConfig 中配置 name、type: modules、android/ios、ios.modulesProvider。
  3. 链接配置:按需在 react-native.config.js 中配置 packageInstance 等。
  4. Android:继承 NativeXxxSpec,实现 getName 及各方法,可选实现 invalidate、emitXxx。
  5. iOS:实现 NativeXxxSpec、RCT_EXPORT_MODULE、getTurboModule 返回 *SpecJSI,实现各方法及可选的 initialize/invalidate、emit。
  6. 封装:业务层通过封装 API 调用模块,不直接暴露 getEnforcing 得到的对象。
上次更新: