跨平台原生模块(C++)
使用 C++ 编写模块是在 Android 和 iOS 之间共享与平台无关代码的最佳方式。借助纯 C++ 模块,你只需编写一次逻辑,就可以立即在所有平台上复用,而无需编写平台特定代码。
在本指南中,我们将介绍如何创建一个纯 C++ Turbo Native Module:
- 创建 JS 规范
- 配置 Codegen 生成脚手架代码
- 实现原生逻辑
- 在 Android 和 iOS 应用中注册模块
- 在 JS 中测试你的改动
本指南其余部分假设你已使用以下命令创建应用:
npx @react-native-community/cli@latest init SampleApp --version latest
1. 创建 JS 规范
纯 C++ Turbo Native Modules 属于 Turbo Native Modules。它们需要一个规范文件(也称为 spec 文件),这样 Codegen 才能为我们创建脚手架代码。规范文件也是我们在 JS 中访问 Turbo Native Module 的方式。
Spec 文件需要使用带类型的 JS 方言编写。React Native 当前支持 Flow 或 TypeScript。
- 在应用根目录下,创建一个名为
specs的新文件夹。 - 创建一个名为
NativeSampleModule.ts的新文件,并写入以下代码:
所有 Native Turbo Module spec 文件都必须以 Native 为前缀,否则 Codegen 会忽略它们。
- TypeScript
- Flow
// @flow
import type {TurboModule} from 'react-native'
import { TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
"NativeSampleModule"
): Spec);
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
2. 配置 Codegen
下一步是在你的 package.json 中配置 Codegen。更新文件,加入以下内容:
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
}
},
"dependencies": {
这个配置告诉 Codegen 去 specs 文件夹中查找 spec 文件。它还指示 Codegen 只为 modules 生成代码,并将生成的代码命名空间设置为 AppSpecs。
3. 编写原生代码
编写 C++ Turbo Native Module 使你能够在 Android 和 iOS 之间共享代码。因此我们只需编写一次代码,然后再看看需要对各个平台进行哪些改动,以便让 C++ 代码被正确加载。
-
创建一个名为
shared的文件夹,和android、ios文件夹处于同一级别。 -
在
shared文件夹中,创建一个名为NativeSampleModule.h的新文件。shared/NativeSampleModule.h#pragma once#include <AppSpecsJSI.h>#include <memory>#include <string>namespace facebook::react {class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {public:NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);std::string reverseString(jsi::Runtime& rt, std::string input);};} // namespace facebook::react -
在
shared文件夹中,创建一个名为NativeSampleModule.cpp的新文件。shared/NativeSampleModule.cpp#include "NativeSampleModule.h"namespace facebook::react {NativeSampleModule::NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker): NativeSampleModuleCxxSpec(std::move(jsInvoker)) {}std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) {return std::string(input.rbegin(), input.rend());}} // namespace facebook::react
我们来看一下刚刚创建的两个文件:
NativeSampleModule.h文件是纯 C++ TurboModule 的头文件。include语句确保我们包含由 Codegen 创建的 spec,其中包含我们需要实现的接口和基类。- 该模块位于
facebook::react命名空间中,以便访问该命名空间中的所有类型。 NativeSampleModule类是实际的 Turbo Native Module 类,它继承自NativeSampleModuleCxxSpec类,后者包含一些胶水代码和样板代码,使该类表现得像一个 Turbo Native Module。- 最后,我们有构造函数,它接受一个指向
CallInvoker的指针,以便在需要时与 JS 通信,以及我们必须实现的函数原型。
NativeSampleModule.cpp 文件则是 Turbo Native Module 的实际实现,它实现了我们在 spec 中声明的构造函数和方法。
4. 在平台中注册模块
接下来的步骤将帮助我们在平台中注册模块。这一步会将原生代码暴露给 JS,这样 React Native 应用最终就可以从 JS 层调用原生方法。
这也是我们唯一一次需要编写一些平台特定代码。
Android
为了确保 Android 应用能够正常构建 C++ Turbo Native Module,我们需要:
- 创建一个
CMakeLists.txt来访问我们的 C++ 代码。 - 修改
build.gradle指向新创建的CMakeLists.txt文件。 - 在 Android 应用中创建一个
OnLoad.cpp文件来注册新的 Turbo Native Module。
1. 创建 CMakeLists.txt 文件
Android 使用 CMake 进行构建。CMake 需要访问我们在 shared 文件夹中定义的文件,才能构建它们。
- 创建一个新文件夹
SampleApp/android/app/src/main/jni。jni文件夹是 Android C++ 侧代码所在的位置。 - 创建一个
CMakeLists.txt文件,并添加以下内容:
cmake_minimum_required(VERSION 3.13)
# 在这里定义库名称。
project(appmodules)
# 这个文件包含了构建 React Native 应用所需的全部内容
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
# 定义附加源代码的位置。我们需要从 jni、main、src、app、android 这些文件夹逐级返回
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ../../../../../shared/NativeSampleModule.cpp)
# 定义 CMake 可以找到附加头文件的位置。这里同样需要从 jni 文件夹逐级返回
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)
CMake 文件执行以下操作:
- 定义
appmodules库,所有应用 C++ 代码都会包含在其中。 - 加载 React Native 基础 CMake 文件。
- 使用
target_sources指令添加构建模块所需的 C++ 源代码。默认情况下,React Native 已经会用默认源文件填充appmodules库,这里我们加入自定义的源码。你可以看到,我们需要从jni文件夹回退到shared文件夹,而我们的 C++ Turbo Module 就在那里。 - 指定 CMake 可以在哪里找到模块头文件。这里同样需要从
jni文件夹回退。
2. 修改 build.gradle 以包含自定义 C++ 代码
Gradle 是协调 Android 构建的工具。我们需要告诉它在哪里可以找到用于构建 Turbo Native Module 的 CMake 文件。
- 打开
SampleApp/android/app/build.gradle文件。 - 在现有的
android块中添加以下内容:
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// 注意!在生产环境中,你需要生成自己的 keystore 文件。
// 参见 https://reactnative.dev/docs/signed-apk-android。
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
+ externalNativeBuild {
+ cmake {
+ path "src/main/jni/CMakeLists.txt"
+ }
+ }
}
这个块告诉 Gradle 文件去哪里查找 CMake 文件。路径是相对于 build.gradle 文件所在目录而言的,因此我们需要添加指向 jni 文件夹中 CMakeLists.txt 文件的路径。
3. 注册新的 Turbo Native Module
最后一步是在运行时中注册新的 C++ Turbo Native Module,这样当 JS 请求该 C++ Turbo Native Module 时,应用就知道从哪里找到它并返回它。
- 在
SampleApp/android/app/src/main/jni文件夹中运行以下命令:
curl -O https://raw.githubusercontent.com/facebook/react-native/main/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp
- 然后按如下方式修改此文件:
#include <DefaultComponentsRegistry.h>
#include <DefaultTurboModuleManagerDelegate.h>
#include <autolinking.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncore.h>
+ // 包含 NativeSampleModule 头文件
+ #include <NativeSampleModule.h>
//...
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
// 这里你可以提供来自你自己的应用或外部库的 CXX Turbo Modules。
// 可遵循如下方式(以名为 `NativeCxxModuleExample` 的模块为例):
//
// if (name == NativeCxxModuleExample::kModuleName) {
// return std::make_shared<NativeCxxModuleExample>(jsInvoker);
// }
+ // 这段代码会注册该模块,以便当 JS 端请求它时,应用可以返回它
+ if (name == NativeSampleModule::kModuleName) {
+ return std::make_shared<NativeSampleModule>(jsInvoker);
+ }
// 然后我们回退到自动链接的 CXX 模块提供者
return autolinking_cxxModuleProvider(name, jsInvoker);
}
// 保留文件其余部分
这些步骤会从 React Native 下载原始的 OnLoad.cpp 文件,这样我们就可以安全地覆盖它,以便在应用中加载 C++ Turbo Native Module。
下载文件后,我们可以按如下方式修改它:
- 引入指向我们模块的头文件
- 注册 Turbo Native Module,以便当 JS 请求它时,应用能够返回它
现在,你可以在项目根目录运行 yarn android,查看你的应用是否成功构建。
iOS
为了确保 iOS 应用能够正常构建 C++ Turbo Native Module,我们需要:
- 安装 pods 并运行 Codegen。
- 将
shared文件夹添加到我们的 iOS 项目中。 - 在应用中注册 C++ Turbo Native Module。
1. 安装 Pods 并运行 Codegen。
我们首先需要执行的是每次准备 iOS 应用时都会执行的常规步骤。CocoaPods 是我们用来设置和安装 React Native 依赖的工具,并且在这个过程中,它还会为我们运行 Codegen。
cd ios
bundle install
bundle exec pod install
2. 将 shared 文件夹添加到 iOS 项目中
这一步将 shared 文件夹添加到项目中,使其在 Xcode 中可见。
- 打开 CocoaPods 生成的 Xcode Workspace。
cd ios
open SampleApp.xcworkspace
- 点击左侧的
SampleApp项目,然后选择Add files to "Sample App"...。

- 选择
shared文件夹并点击Add。

如果一切正确,左侧的项目应如下所示:

3. 在应用中注册 Cxx Turbo Native Module
要在你的应用中注册一个纯 Cxx Turbo Native Module,你需要:
- 为原生模块创建一个
ModuleProvider - 配置
package.json,将 JS 模块名称与ModuleProvider类关联起来。
ModuleProvider 是一个 Objective-C++ 文件,它将纯 C++ 模块与 iOS 应用的其余部分连接起来。
3.1 创建 ModuleProvider
- 在 Xcode 中,选择
SampleApp项目并按下 ⌘ + N 来创建一个新文件。 - 选择
Cocoa Touch Class模板 - 添加名称
NativeSampleModuleProvider(保留其他字段为Subclass of: NSObject和Language: Objective-C) - 点击 Next 生成文件。
- 将
NativeSampleModuleProvider.m重命名为NativeSampleModuleProvider.mm。mm扩展名表示这是一个 Objective-C++ 文件。 - 使用以下内容实现
NativeSampleModuleProvider.h:
#import <Foundation/Foundation.h>
#import <ReactCommon/RCTTurboModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface NativeSampleModuleProvider : NSObject <RCTModuleProvider>
@end
NS_ASSUME_NONNULL_END
这声明了一个遵循 RCTModuleProvider 协议的 NativeSampleModuleProvider 对象。
- 使用以下内容实现
NativeSampleModuleProvider.mm:
#import "NativeSampleModuleProvider.h"
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import "NativeSampleModule.h"
@implementation NativeSampleModuleProvider
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeSampleModule>(params.jsInvoker);
}
@end
这段代码通过在调用 getTurboModule: 方法时创建纯 C++ 的 NativeSampleModule 来实现 RCTModuleProvider 协议。
3.2 更新 package.json
最后一步是更新 package.json,以告知 React Native JS 侧的原生模块规范与其在原生代码中的具体实现之间的关联。
按如下方式修改 package.json:
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
},
"ios": {
"modulesProvider": {
"NativeSampleModule": "NativeSampleModuleProvider"
}
}
},
"dependencies": {
此时,你需要重新安装 pods,以确保 Codegen 再次运行并生成新文件:
# 来自 ios 文件夹
bundle exec pod install
open SampleApp.xcworkspace
如果现在从 Xcode 构建你的应用,你应该能够成功构建。
5. 测试你的代码
现在是时候从 JS 中访问我们的 C++ Turbo Native Module 了。为此,我们需要修改 App.tsx 文件,以导入 Turbo Native Module 并在代码中调用它。
- 打开
App.tsx文件。 - 将模板内容替换为以下代码:
import React from 'react';
import {
Button,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import SampleTurboModule from './specs/NativeSampleModule';
function App(): React.JSX.Element {
const [value, setValue] = React.useState('');
const [reversedValue, setReversedValue] = React.useState('');
const onPress = () => {
const revString = SampleTurboModule.reverseString(value);
setReversedValue(revString);
};
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
欢迎使用 C++ Turbo Native Module 示例
</Text>
<Text>在这里写下你想要反转的文本</Text>
<TextInput
style={styles.textInput}
placeholder="在这里输入你的文本"
onChangeText={setValue}
value={value}
/>
<Button title="反转" onPress={onPress} />
<Text>反转后的文本:{reversedValue}</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
marginBottom: 20,
},
textInput: {
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 10,
marginTop: 10,
},
});
export default App;
这个应用中有几行值得注意:
import SampleTurboModule from './specs/NativeSampleModule';:这一行在应用中导入了 Turbo Native Module,const revString = SampleTurboModule.reverseString(value);位于onPress回调中:这就是你在应用中使用 Turbo Native Module 的方式。
为了这个示例并尽可能保持简短,我们直接在应用中导入了 spec 文件。 在这种情况下,最佳实践是创建一个单独的文件来封装这些 spec,并在你的应用中使用该文件。 这样可以让你为这些 spec 准备输入,并且在 JS 中对它们有更多控制。
恭喜你,你已经编写了第一个 C++ Turbo Native Module!
![]() | ![]() |

