Skip to main content

Turbo 模块作为传统原生模块


提醒

本文档仍为 experimental,详细信息可能会随着我们的迭代而发生变化。 欢迎在此页面分享你对 工作组内部讨论 的反馈。

This documentation is still experimental and details are subject to changes as we iterate. Feel free to share your feedback on the discussion inside the working group for this page.

而且,它还包含几个 手动步骤。 请注意,一旦新架构稳定,这将不代表最终的开发者体验。 我们正在开发工具、模板和库,以帮助你快速开始使用新架构,而无需完成整个设置。

Moreover, it contains several manual steps. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup.

信息

创建向后兼容的 Turbo Native Module 需要了解如何创建 Legacy Native Module。 要回忆这些概念,请查看此 guide

Creating a backward compatible Turbo Native Module requires the knowledge of how to create a Legacy Native Module. To recall these concepts, have a look at this guide.

TurboModules 仅在正确设置新架构时才起作用。 如果你已经有一个库想要迁移到新架构,也可以看看 迁移指南

创建向后兼容的 TurboModule 可以让你的用户继续利用你的库,而与他们使用的体系结构无关。 创建这样的模块需要几个步骤:

Creating a backward compatible TurboModule lets your users continue to leverage your library, independently from the architecture they use. The creation of such a module requires a few steps:

  1. 配置库,以便为旧架构和新架构准备好正确设置的依赖。
  2. 更新代码库,以便新架构类型在不可用时不会被编译。
  3. 统一 JavaScript API,这样你的用户代码就不需要更改。
信息

为了本指南的目的,我们将使用以下 terminology

For the sake of this guide we're going to use the following terminology:

  • 旧版原生模块 - 指在旧的 React Native 架构上运行的模块。
  • Turbo Native 模块 - 指已适应新原生模块系统的模块。 为了简洁起见,你可能会发现它们被称为 涡轮增压模块
提醒

对新架构的 TypeScript 支持仍处于测试阶段。

虽然最后一步对于所有平台都是相同的,但前两个步骤对于 iOS 和 Android 来说是不同的。

While the last step is the same for all the platforms, the first two steps are different for iOS and Android.

配置 Turbo Native 模块依赖

iOS

Apple 平台使用 CocoaPods 作为依赖管理器来安装 Turbo Native Modules。

The Apple platform installs Turbo Native Modules using CocoaPods as a dependency manager.

如果你已经使用 install_module_dependencies 功能,则使用 没有什么可以做的。 该函数已经在启用新架构时负责安装正确的依赖,并在未启用时避免安装它们。

If you are already using the install_module_dependencies function, then there is nothing to do. The function already takes care of installing the proper dependencies when the New Architecture is enabled and avoids them when it is not enabled.

否则,你的 Turbo Native 模块的 podspec 应如下所示:

Otherwise, your Turbo Native Module's podspec should look like this:

require "json"

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

folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'

Pod::Spec.new do |s|
# Default fields for a valid podspec
s.name = "<TM Name>"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"
# React Native Core dependency
s.dependency "React-Core"

# The following lines are required by the New Architecture.
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}

s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"

end

你应该在启用新架构时安装额外的依赖,并在未启用时避免安装它们。 为此,你可以使用 install_modules_dependencies。 更新 .podspec 文件,如下所示:

You should install the extra dependencies when the New Architecture is enabled, and avoid installing them when it's not. To achieve this, you can use the install_modules_dependencies. Update the .podspec file as it follows:

require "json"

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

-folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'

Pod::Spec.new do |s|
# Default fields for a valid podspec
s.name = "<TM Name>"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"
# React Native Core dependency
+ install_modules_dependencies(s)
- s.dependency "React-Core"
-
- # The following lines are required by the New Architecture.
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
- s.pod_target_xcconfig = {
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
- }
-
- s.dependency "React-Codegen"
- s.dependency "RCT-Folly"
- s.dependency "RCTRequired"
- s.dependency "RCTTypeSafety"
- s.dependency "ReactCommon/turbomodule/core"
end

安卓

要创建可以使用两种架构的模块,你需要配置 Gradle 以根据所选架构选择需要编译的文件。 这可以通过在 Gradle 配置中使用 不同的源集 来实现。

To create a module that can work with both architectures, you need to configure Gradle to choose which files need to be compiled depending on the chosen architecture. This can be achieved by using different source sets in the Gradle configuration.

注意

请注意,这是目前建议的方法。 虽然这可能会导致一些代码重复,但它将确保与两种架构的最大兼容性。 你将在下一节中看到如何减少重复。

要配置 Turbo Native 模块以便它选择正确的源集,你必须按以下方式更新 build.gradle 文件:

To configure the Turbo Native Module so that it picks the proper sourceset, you have to update the build.gradle file in the following way:

build.gradle
+// Add this function in case you don't have it already
+ def isNewArchitectureEnabled() {
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
+}


// ... other parts of the build file

defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 31)
+ buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString())
+ }
+
+ sourceSets {
+ main {
+ if (isNewArchitectureEnabled()) {
+ java.srcDirs += ['src/newarch']
+ } else {
+ java.srcDirs += ['src/oldarch']
+ }
+ }
}
}

这一变化主要做了三件事:

This changes do three main things:

  1. 第一行定义了一个函数,该函数返回新架构是否启用。
  2. buildConfigField 行定义了一个名为 IS_NEW_ARCHITECTURE_ENABLED 的构建配置布尔字段,并使用第一步中声明的函数对其进行初始化。 这允许你在运行时检查用户是否指定了 newArchEnabled 属性。
  3. 最后几行利用第一步中声明的函数来决定我们需要构建哪些源集,具体取决于所选的体系结构。

更新代码库

iOS

第二步是指示 Xcode 在我们使用旧架构构建应用时避免使用新架构类型和文件编译所有行。

The second step is to instruct Xcode to avoid compiling all the lines using the New Architecture types and files when we are building an app with the Old Architecture.

有两个文件需要更改。 模块实现文件,通常是 <your-module>.mm 文件,模块头,通常是 <your-module>.h 文件。

There are two files to change. The module implementation file, which is usually a <your-module>.mm file, and the module header, which is usually a <your-module>.h file.

该实现文件的结构如下:

That implementation file is structured as follows:

  • 一些 #import 语句,其中有一个 <GeneratedSpec>.h 文件。
  • 模块实现,使用各种 RCT_EXPORT_xxxRCT_REMAP_xxx 宏。
  • getTurboModule: 函数,使用 <MyModuleSpecJSI> 类型,由新架构生成。

goal 是为了确保 Turbo Native Module 仍然使用旧架构构建。 为此,我们可以将 #import "<GeneratedSpec>.h"getTurboModule: 函数封装到 #ifdef RCT_NEW_ARCH_ENABLED 编译指令中,如下例所示:

The goal is to make sure that the Turbo Native Module still builds with the Old Architecture. To achieve that, we can wrap the #import "<GeneratedSpec>.h" and the getTurboModule: function into an #ifdef RCT_NEW_ARCH_ENABLED compilation directive, as shown in the following example:

#import "<MyModuleHeader>.h"
+ #ifdef RCT_NEW_ARCH_ENABLED
#import "<GeneratedSpec>.h"
+ #endif

// ... rest of your module

+ #ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::<MyModuleSpecJSI>>(params);
}
+ #endif

@end

头文件也需要做类似的事情。 在模块标题的底部添加以下行。 你需要首先导入标头,然后,如果启用了 New Architecture,则使其符合 Spec 协议。

A similar thing needs to be done for the header file. Add the following lines at the bottom of your module header. You need to first import the header and then, if the New Architecture is enabled, make it conform to the Spec protocol.

#import <React/RCTBridgeModule.h>
+ #ifdef RCT_NEW_ARCH_ENABLED
+ #import <YourModuleSpec/YourModuleSpec.h>
+ #endif

@interface YourModule: NSObject <RCTBridgeModule>

@end

+ #ifdef RCT_NEW_ARCH_ENABLED
+ @interface YourModule () <YourModuleSpec>

+ @end
+ #endif

此代码片段使用与之前的 section 中使用的相同的 RCT_NEW_ARCH_ENABLED 标志。 如果未设置此标志,Xcode 在编译期间会跳过 #ifdef 中的行,并且不会将它们包含到编译的二进制文件中。

This snippets uses the same RCT_NEW_ARCH_ENABLED flag used in the previous section. When this flag is not set, Xcode skips the lines within the #ifdef during compilation and it does not include them into the compiled binary.

安卓

由于我们无法在 Android 上使用条件编译块,因此我们将定义两个不同的源集。 这将允许创建一个向后兼容的 Turbo Native 模块,并根据所使用的架构加载和编译适当的源代码。

As we can't use conditional compilation blocks on Android, we will define two different source sets. This will allow to create a backward compatible Turbo Native Module with the proper source that is loaded and compiled depending on the used architecture.

因此,你必须:

Therefore, you have to:

  1. src/oldarch 路径中创建旧原生模块。 请参阅 本指南 了解如何创建旧原生模块。
  2. src/newarch 路径中创建 Turbo Native 模块。 请参阅 本指南 了解如何创建 Turbo Native 模块

然后指示 Gradle 决定选择哪个实现。

and then instruct Gradle to decide which implementation to pick.

一些文件可以在旧原生模块和 Turbo Native 模块之间共享: 这些应该被创建或移动到由两种架构加载的文件夹中。 这些文件是:

Some files can be shared between a Legacy Native Module and a Turbo Native Module: these should be created or moved into a folder that is loaded by both the architectures. These files are:

  • 用于加载模块的 <MyModule>Package.java 文件。
  • 一个 <MyTurboModule>Impl.java 文件,我们可以在其中放置 Legacy Native Module 和 Turbo Native Module 必须执行的代码。

最终的文件夹结构如下所示:

The final folder structure looks like this:

my-module
├── android
│ ├── build.gradle
│ └── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── mymodule
│ │ ├── MyModuleImpl.java
│ │ └── MyModulePackage.java
│ ├── newarch
│ │ └── java
│ │ └── com
│ │ └── MyModule.java
│ └── oldarch
│ └── java
│ └── com
│ └── MyModule.java
├── ios
├── js
└── package.json

MyModuleImpl.java 中应包含的且可以由 Legacy Native Module 和 Turbo Native Module 共享的代码例如是:

The code that should go in the MyModuleImpl.java, and that can be shared by the Legacy Native Module and the Turbo Native Module is, for example:

example of MyModuleImpl.java
package com.mymodule;

import androidx.annotation.NonNull;
import com.facebook.react.bridge.Promise;
import java.util.Map;
import java.util.HashMap;

public class MyModuleImpl {

public static final String NAME = "MyModule";

public void foo(double a, double b, Promise promise) {
// implement the logic for foo and then invoke promise.resolve or
// promise.reject.
}
}

然后,可以通过以下步骤更新 Legacy Native Module 和 Turbo Native Module:

Then, the Legacy Native Module and the Turbo Native Module can be updated with the following steps:

  1. 创建 MyModuleImpl 类的私有实例。
  2. 在模块构造函数中初始化实例。
  3. 在模块方法中使用私有实例。

例如,对于旧原生模块:

For example, for a Legacy Native Module:

Native Module using the Impl module
public class MyModule extends ReactContextBaseJavaModule {

// declare an instance of the implementation
private MyModuleImpl implementation;

MyModule(ReactApplicationContext context) {
super(context);
// initialize the implementation of the module
implementation = MyModuleImpl();
}

@Override
public String getName() {
// NAME is a static variable, so we can access it using the class name.
return MyModuleImpl.NAME;
}

@ReactMethod
public void foo(int a, int b, Promise promise) {
// Use the implementation instance to execute the function.
implementation.foo(a, b, promise);
}
}

并且,对于 Turbo Native 模块:

And, for a Turbo Native Module:

TurboModule using the Impl module
public class MyModule extends MyModuleSpec {
// declare an instance of the implementation
private MyModuleImpl implementation;

MyModule(ReactApplicationContext context) {
super(context);
// initialize the implementation of the module
implementation = MyModuleImpl();
}

@Override
@NonNull
public String getName() {
// NAME is a static variable, so we can access it using the class name.
return MyModuleImpl.NAME;
}

@Override
public void foo(double a, double b, Promise promise) {
// Use the implementation instance to execute the function.
implementation.foo(a, b, promise);
}
}

有关如何实现此目标的分步示例,请查看 这个仓库

For a step-by-step example on how to achieve this, have a look at this repo.

统一 JavaScript 规范

提醒

对新架构的 TypeScript 支持仍处于测试阶段。

最后一步确保 JavaScript 对所选架构的行为是透明的。

The last step makes sure that the JavaScript behaves transparently to chosen architecture.

对于 Turbo Native 模块,真实来源是 Native<MyModule>.js(或 .ts)规范文件。 该应用像这样访问规范文件:

For a Turbo Native Module, the source of truth is the Native<MyModule>.js (or .ts) spec file. The app accesses the spec file like this:

import MyModule from 'your-module/src/index';

由于 TurboModuleRegistry.get 在底层利用了旧的 Native Modules API,我们需要重新导出我们的模块,以避免多次注册。

Since TurboModuleRegistry.get taps into the old Native Modules API under the hood, we need to re-export our module, to avoid registering it multiple times.

// @flow
export default require('./Native<MyModule>').default;