React Native 新架构原生模块开发指南
本文介绍如何基于 TurboModule(新架构原生模块)开发原生模块,内容基于 react-native-troika 仓库中的 packages/ 下的 overlay 等实现整理。
建议:以独立库的方式编写原生模块,便于复用与发布。
可使用 react-native-create-lib 一行命令生成库脚手架(支持新架构与 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'为原生注册的模块名(AndroidgetName()、iOSRCT_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 会生成NativeOverlaySpecBase的emitOnShown:,在原生侧在合适时机调用即可。
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.modulesProvider:RN 模块名 → 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 注册
在自定义 ReactPackage 的 createNativeModules 中返回模块实例;若使用 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-edge 的 EdgeToEdgeModule 仍为旧架构(ReactContextBaseJavaModule + @ReactMethod);overlay 为新架构 TurboModule,可对照两者差异。
六、小结
- TS 规范:建
NativeXxx.ts,Spec extends TurboModule,TurboModuleRegistry.getEnforcing<Spec>('ModuleName')。 - Codegen 配置:在 package.json 的 codegenConfig 中配置 name、type: modules、android/ios、ios.modulesProvider。
- 链接配置:按需在 react-native.config.js 中配置 packageInstance 等。
- Android:继承 NativeXxxSpec,实现 getName 及各方法,可选实现 invalidate、emitXxx。
- iOS:实现 NativeXxxSpec、RCT_EXPORT_MODULE、getTurboModule 返回 *SpecJSI,实现各方法及可选的 initialize/invalidate、emit。
- 封装:业务层通过封装 API 调用模块,不直接暴露 getEnforcing 得到的对象。