跳到主要内容
新架构实战课 实操 + 基建 + 原理全维度包揽,抢先掌握 React Native 新架构精髓 立即查看 >

C++ Turbo 原生模块

注意

这个文档仍然是实验性的,随着我们的迭代,细节会有变化。欢迎在工作小组内的讨论中分享你的反馈。

此外,它还包含几个手动步骤。请注意新架构尚未稳定下来,最终的开发者体验会继续迭代改善。我们正在努力开发工具、模板和库,以帮助你在新架构上快速入门,而不需要经历整个设置过程。

本指南将向您展示如何仅使用 C++ 实现Turbo 原生模块,以便与任何支持的平台(Android、iOS、macOS或Windows)共享相同的实现。

在继续本指南之前,请阅读Turbo Native Modules部分。作为进一步参考,我们为 RNTester 应用准备了一个示例(NativeCxxModuleExample),并在我们的社区代码库中提供了另一个示例(run/pure-cxx-module)。

注意

使用 C++ Turbo 原生模块需要启用新架构。 要迁移到新架构,请按照迁移指南进行操作。

如何创建 C++ Turbo 原生模块

要创建 C++ Turbo 原生模块,您需要:

  1. 定义JavaScript规范。
  2. 配置Codegen以生成脚手架。
  3. 注册本地模块。
  4. 编写本机代码来完成模块的实现。

为新架构设置一个测试应用

第一步,创建一个新的应用程序:

npx react-native init CxxTurboModulesGuide
cd CxxTurboModulesGuide
yarn install

在 Android 上通过修改 android/gradle.properties 文件来启用新架构:

- newArchEnabled=false
+ newArchEnabled=true

在 iOS 上,在 ios 文件夹中运行 pod install 时启用新架构:

RCT_NEW_ARCH_ENABLED=1 bundle exec pod install

Turbo 模块文件夹设置

在项目中创建一个tm文件夹。它将包含您的应用程序的所有C++ Turbo模块。最终结果应该如下所示:

CxxTurboModulesGuide
├── android
├── ios
├── js
└── tm

1. JavaScript 规范

tm 文件夹中创建以下规范:

NativeSampleModule.ts
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
// import type {TurboModule} from 'react-native'; in future versions
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}

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

2. Codegen 配置

接下来,您需要为 Codegen 添加一些配置。

应用

请在您的应用的 package.json 文件中更新以下条目:

package.json
{
// ...
"description": "React Native with Cxx Turbo Native Modules",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"homepage": "https://github.com/<your_github_handle>/#readme",
// ...
"codegenConfig": {
"name": "AppSpecs",
"type": "all",
"jsSrcsDir": "tm",
"android": {
"javaPackageName": "com.facebook.fbreact.specs"
}
}
}

它添加了必要的属性,我们将在 iOS 的 podspec 文件中重新使用,并配置 Codegentm 文件夹内搜索规范。

注意

C++ Turbo 原生模块不会自动链接,需要按照下面描述的步骤手动包含到应用程序中。

iOS: 创建 podspec 文件

在 iOS 上,您需要在tm文件夹中创建一个名为AppTurboModules.podspec的文件 - 它将如下所示:

AppTurboModules.podspec
require "json"

package = JSON.parse(File.read(File.join(__dir__, "../package.json")))

Pod::Spec.new do |s|
s.name = "AppTurboModules"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "12.4" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }
s.source_files = "**/*.{h,cpp}"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
install_modules_dependencies(s)
end

你需要将它作为依赖项添加到你的应用程序中的 ios/Podfile 文件中,例如,在 use_react_native!(...) 部分之后:

if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
pod 'AppTurboModules', :path => "./../tm"
end

Android: build.gradle, CMakeLists.txt, Onload.cpp

对于 Android,您需要在tm文件夹中创建一个名为CMakeLists.txt的文件 - 其内容如下:

cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)

add_compile_options(
-fexceptions
-frtti
-std=c++17)

file(GLOB tm_SRC CONFIGURE_DEPENDS *.cpp)
add_library(tm STATIC ${tm_SRC})

target_include_directories(tm PUBLIC .)
target_include_directories(react_codegen_AppSpecs PUBLIC .)

target_link_libraries(tm
jsi
react_nativemodule_core
react_codegen_AppSpecs)

它将tm文件夹定义为本地代码的来源,并设置了必要的依赖项。

您需要将其添加为应用程序的依赖项,例如在android/app/build.gradle文件的末尾:

build.gradle
android {
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
}
}
}
备注

确保选择正确的 android/app/build.gradle 文件,而不是 android/build.gradle。

3. 注册模块

iOS

要在您的应用程序中注册一个C++ Turbo 原生模块,您需要在ios/CxxTurboModulesGuide/AppDelegate.mm文件中更新以下条目:

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
+ #import <React/CoreModulesPlugins.h>
+ #import <ReactCommon/RCTTurboModuleManager.h>
+ #import <NativeSampleModule.h>

+ @interface AppDelegate () <RCTTurboModuleManagerDelegate> {}
+ @end

// ...

᠆ (Class)getModuleClassFromName:(const char *)name
{
return RCTCoreModulesClassProvider(name);
}

+ #pragma mark RCTTurboModuleManagerDelegate

+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
+ jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
+ {
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
+ return nullptr;
+ }

这将实例化一个与之前在我们的 JavaScript 规范文件中定义的名称为NativeSampleModuleNativeSampleModule相关联的对象。

Android

Android应用默认情况下没有设置原生代码编译。

  1. 创建文件夹 android/app/src/main/jni
  2. node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup复制CMakeLists.txtOnload.cppandroid/app/src/main/jni 文件夹中。

使用以下条目更新 Onload.cpp:

// ...

#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncli.h>
+ #include <NativeSampleModule.h>

// ...

std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker) {
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
return nullptr;
}

// ...

更新CMakeLists.txt文件,添加以下条目,例如,在该文件的末尾处:

// ...

# This file includes all the necessary to let you build your application with the New Architecture.
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)

+ # App needs to add and link against tm (TurboModules) folder
+ add_subdirectory(${REACT_ANDROID_DIR}/../../../tm/ tm_build)
+ target_link_libraries(${CMAKE_PROJECT_NAME} tm)

这将实例化一个与之前在我们的 JavaScript 规范文件中定义的名称为NativeSampleModuleNativeSampleModule相关联的对象。

4. C++ 原生代码

在最后一步中,您需要编写一些原生代码来连接JavaScript端和本地平台。这个过程包括两个主要步骤:

  • 运行Codegen以查看它生成了什么。
  • 编写您的原生代码,实现生成的接口。

运行 Codegen

提示

阅读 Codegen 指南获取更多信息。

在iOS上,每次在ios文件夹中执行时都会运行Codegen:

RCT_NEW_ARCH_ENABLED=1 bundle exec pod install

您可以检查位于CxxTurboModulesGuide/ios/build/generated/ios文件夹内的生成的 AppSpecsJSI.hAppSpecsJSI-generated.cpp 文件。

这些文件以 AppSpecs 为前缀,因为这与之前添加到 package.jsoncodegenConfig.name 参数匹配。

在 Android 上,每次执行以下命令时都会运行 Codegen:

yarn android

您可以检查位于 CxxTurboModulesGuide/android/app/build/generated/source/codegen/jni 文件夹内的生成的 AppSpecsJSI.hAppSpecsJSI-generated.cpp 文件。

只有当您更改了 JavaScript 规范时才需要重新运行codegen。

JavaScript 规范文件生成的C++函数如下:

virtual jsi::String reverseString(jsi::Runtime &rt, jsi::String input) = 0;

你可以直接使用较低级别的 jsi:: 类型进行工作,但为了方便起见,C++ Turbo Native 模块会自动将其桥接到 std:: 类型。

实现

现在创建一个名为 NativeSampleModule.h 的文件,内容如下:

备注

由于 CMake 和 CocoaPod 设置的当前差异,我们需要一些技巧来在每个平台上包含正确的 Codegen 头文件。

#pragma once

#if __has_include(<React-Codegen/AppSpecsJSI.h>) // CocoaPod headers on Apple
#include <React-Codegen/AppSpecsJSI.h>
#elif __has_include("AppSpecsJSI.h") // CMake headers on Android
#include "AppSpecsJSI.h"
#endif
#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

在这种情况下,您可以使用任何与jsi::String相对应的C++类型 - 默认类型或自定义类型。但是,您不能指定不兼容的类型,例如boolfloat或者 std::vector<>,因为它们无法与 jsi::String 进行“桥接”,从而导致编译错误。

现在,请添加一个名为 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

我们在ios文件夹中添加了新的C++文件,如下所示:

RCT_NEW_ARCH_ENABLED=1 bundle exec pod install

对于iOS来说,在Xcode中它们会出现在Pods目标下的Development Pods \ TurboModules子文件夹中。

现在你应该能够同时编译你的Android和iOS应用程序了。

CxxTurboModulesGuide
├── android
│ └── app
│ │── src
│ │ └── main
│ │ └── jni
│ │ ├── CMakeLists.txt
│ │ └── OnLoad.cpp
│ └── build.gradle (updated)
├── ios
│ └── CxxTurboModulesGuide
│ └── AppDelegate.mm (updated)
├── js
│ └── App.tsx|jsx (updated)
└── tm
├── CMakeLists.txt
├── NativeSampleModule.h
├── NativeSampleModule.cpp
├── NativeSampleModule.ts|js
└── TurboModules.podspec

5. 将C++ Turbo 原生模块添加到您的应用程序

为了演示目的,我们可以在我们的应用程序的App.tsx|jsx中更新以下条目:

//...
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
+ import NativeSampleModule from './tm/NativeSampleModule';
//...
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
+ <Section title="Cxx TurboModule">
+ NativeSampleModule.reverseString(...) ={' '}
+ {NativeSampleModule.reverseString(
+ 'the quick brown fox jumps over the lazy dog'
+ )}
+ </Section>;
<Section title="Step One">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
screen and then come back to see your edits.
</Section>
//...

运行应用以查看您的C++ Turbo 原生模块的效果!

App TurboModuleProvider [可选]

通过声明一个AppTurboModuleProvider,您可以避免在添加多个C++ Turbo 原生模块时出现一些代码重复:

AppTurboModuleProvider.h
#pragma once

#include <ReactCommon/TurboModuleBinding.h>
#include <memory>
#include <string>

namespace facebook::react {

class AppTurboModuleProvider {
public:
std::shared_ptr<TurboModule> getTurboModule(
const std::string& name,
std::shared_ptr<CallInvoker> jsInvoker) const;
};

} // namespace facebook::react

相应实现:

AppTurboModuleProvider.cpp
#include "AppTurboModuleProvider.h"
#include "NativeSampleModule.h"

namespace facebook::react {

std::shared_ptr<TurboModule> AppTurboModuleProvider::getTurboModule(
const std::string& name,
std::shared_ptr<CallInvoker> jsInvoker) const {
if (name == "NativeSampleModule") {
return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
}
// Other C++ Turbo Native Modules for you app
return nullptr;
}

} // namespace facebook::react

然后在Android的OnLoad.cpp和iOS的AppDelegate.mm相应的函数中复用它:

static facebook::react::AppTurboModuleProvider appTurboModuleProvider;
return appTurboModuleProvider.getTurboModule(name, jsInvoker);

调用特定操作系统的 API

您仍然可以在编译单元中调用特定于操作系统的函数(例如,在苹果上使用NS/CF API或在Windows上使用Win32/WinRT API),只要方法签名只使用 std::jsi:: 类型。

对于苹果特定的API,您需要将实现文件的扩展名从.cpp更改为.mm,以便能够使用 NS/CF API。

扩展 C++ Turbo 原生模块

如果您需要支持尚未支持的某些类型,请参阅此其他指南