TurboModules
这个文档仍然是实验性的,随着我们的迭代,细节会有变化。欢迎在工作小组内的讨论中分享你的反馈。
此外,它还包含几个手动步骤。请注意新架构尚未稳定下来,最终的开发者体验会继续迭代改善。我们正在努力开发工具、模板和库,以帮助你在新架构上快速入门,而不需要经历整个设置过程。
如果您使用过 React Native,您可能了解过 Native Modules 这个概念。它可以通过 React Native 的「Bridge」帮助 JavaScript 和原生代码进行交互,并使用跨平台的数据格式 JSON 进行通讯。
Turbo Native Modules 与 Native Modules 相比,存在以下优势:
- 各个平台的强类型接口声明是一致的;
- 您可以使用 C++ 编写模块或迁移其它平台的原生代码,以此避免在跨平台重复实现模块;
- 模块支持懒加载,可以加快 App 启动速度;
- 通过替换 Bridge 为 JSI(使用原生代码编写的 JavaScript 接口),提升 JavaScript 与原生代码的通讯效率。
本文档将指导您如何创建一个兼容 React Native 0.70.0 的基础 Turbo Native Module 。
使用 Turbo Native Module 前必须开启新架构。若要了解如何将代码迁移到新架构,您可参考此迁移指南。
如何创建 TurboModule
创建一个 Turbo Native Module 分为以下步骤:
- 声明 JavaScript 接口类型;
- 配置模块以支持 Codegen 自动生成脚手架代码;
- 编写原生代码完成模块实现。
1. 目录配置
为了使模块与 App 保持解耦,有个不错的方案是将模块从 App 抽离出来,并添加为 App 的一个依赖。如果您打算开发一个开源的 Turbo Native Module,您同样也需要这么做。
打开您的 App,创建一个名为 RTNCalculator
的目录。RTN 的意思是「React Native」,同时它也是推荐的 React Native 模块前缀命名。
在 RTNCalculator
目录中,创建三个子目录:js
、ios
和 android
。创建后的目录结构是这样的:
TurboModulesGuide
├── MyApp
└── RTNCalculator
├── android
├── ios
└── js
2. 声明 JavaScript 接口
新架构要求必须使用强类型风格语言声明 JavaScript 接口(Flow 和 TypeScript 皆可)。Codegen
会根据这些接口声明来生成强类型的语言,其中包括 C++、Objective-C 和 Java。
对于声明类型的代码文件必须满足以下两点要求:
- 文件必须使用
Native<MODULE_NAME>
命名,在使用 Flow 时,以.js
或.jsx
为后缀名;在使用 Typescript 时,以.ts
或.tsx
为后缀名。Codegen 只会找到匹配这些命名规则的文件; - 代码中必须要输出
TurboModuleRegistrySpec
对象
- flow
- typescript
// @flow
import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
add(a: number, b: number): Promise<number>;
}
export default (TurboModuleRegistry.get<Spec>(
'RTNCalculator'
): ?Spec);
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
add(a: number, b: number): Promise<number>;
}
export default TurboModuleRegistry.get<Spec>(
'RTNCalculator',
) as Spec | null;
在代码顶部需导入以下两个声明文件:
- 类型
TurboModule
:定义 Turbo Native Module 的基础接口 - JS 模块
TurboModuleRegistry
:包含了用于加载 Turbo Native Module 的函数
代码的第二个部分就是针对 Turbo Native Module 的接口声明。在本例中,接口声明了 add
函数,它将用于接受两个数字并返回一个包装数字的 Promise。为声明 Turbo Native Module,此接口必须命名为 Spec
。
最后,调用 TurboModuleRegistry.get
并传入模块名,它将在 Turbo Native Module 可用的时候进行加载。
当我们在编写 JavaScript 代码时,如果没有配置好对应的模块或依赖安装,就从第三方库导入类型,可能会使的您的 IDE 不能正确载入导入声明,从而显示错误或警告。这种情况是正常的,它不会在您添加模块到 App 的时候出现问题。
3. 模块配置
接下来,您需要为 Codegen 和自动链接添加一些配置。
有一些配置文件在 iOS 和 Android 平台是通用的,而有的仅能在某一平台使用。
Shared
shared 是 package.json
文件中的一个配置项,它将在 yarn 安装您的模块时被调用。请在 RTNCalculator
的根目录创建 package.json
文件。
{
"name": "rtn-calculator",
"version": "0.0.1",
"description": "Add numbers with TurboModules",
"react-native": "js/index",
"source": "js/index",
"files": [
"js",
"android",
"ios",
"rtn-calculator.podspec",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"keywords": ["react-native", "ios", "android"],
"repository": "https://github.com/<your_github_handle>/rtn-calculator",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"bugs": {
"url": "https://github.com/<your_github_handle>/rtn-calculator/issues"
},
"homepage": "https://github.com/<your_github_handle>/rtn-calculator#readme",
"devDependencies": {},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"codegenConfig": {
"name": "RTNCalculatorSpec",
"type": "modules",
"jsSrcsDir": "js",
"android": {
"javaPackageName": "com.rtncalculator"
}
}
}
文件上面的内容包含了一些描述性的信息,比如组件名、版本和代码文件。务必记得设置使用 <>
包裹的占位符,如替换所有的<your_github_handle>
、<Your Name>
、<your_email@your_provider.com>
等标记。
接下来是 npm 包的依赖。在本指导中,您会用到 react
和 react-native
。
最后,将 Codegen 的配置声明到 codegenConfig
字段。codegenConfig
是一个用于存放要生成的第三方库的对象数组,每个对象又包含其它三个字段:
name
:第三方库的名称。按照惯例,名称应以Spec
为结尾type
:在这个 npm 包里的模块类型。在本例中,我们开发的是 Turbo Native Module,所以值为modules
jsSrcsDir
:用于找到js
接口声明文件的相对路径,它将被 Codegen 解析android.javaPackageName
:由 Codegen 生成的 Java 包名 (需与 AndroidManifest.xml 中包名一致)
iOS:创建 podspec
文件
针对 iOS 平台,您需要创建一个 rtn-calculator.podspec
文件,它将您的模块定义为 App 里的一个依赖。文件要放在 RTNCalculator
根目录,与 ios
目录处于同一个位置。
文件内容如下:
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "rtn-calculator"
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}"
install_modules_dependencies(s)
end
这个 .podspec
文件需要和 package.json
处于同一个目录,并且它的命名应该取自我们在 package.json
的 name
字段配置的值:rtn-calculator
。
文件首部分设置了一些在后续会使用到的变量。后面一部分内容是一些用来配置 pod 的信息,比如命名、版本和功能描述等。其余内容是一些在新架构中必须要配置的依赖。
Android: build.gradle
, AndroidManifest.xml
, ReactPackage
类
若要在 Android 平台运行 Codegen,您需要创建三个文件:
- 带有
Codegen
配置信息的build.gradle
文件 AndroidManifest.xml
- 一个实现
ReactPackage
接口的 Java 类
在文件创建完成后,android
目录文件结构应该是这样的:
android
├── build.gradle
└── src
└── main
├── AndroidManifest.xml
└── java
└── com
└── rtncalculator
└── CalculatorPackage.java
build.gradle
首先,在 android
目录创建 build.gradle
文件,并配置以下内容:
buildscript {
ext.safeExtGet = {prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
repositories {
google()
gradlePluginPortal()
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.1")
}
}
apply plugin: 'com.android.library'
apply plugin: 'com.facebook.react'
android {
compileSdkVersion safeExtGet('compileSdkVersion', 31)
}
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../node_modules/react-native/android"
}
mavenCentral()
google()
}
dependencies {
implementation 'com.facebook.react:react-native:+'
}
AndroidManifest.xml
其次,创建 android/src/main
目录,然后在这个目录内创建 AndroidManifest.xml
文件,并编写以下代码:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rtncalculator">
</manifest>
这个 manifest 文件的用途是声明您开发的模块的 Java 包。
ReactPackage
类
最后,您需要一个继承 TurboReactPackage
接口的类。在运行 Codegen 前,您不用完整实现这个类。对于 App 而言,一个没有实现接口的空类就已经能当做一个 React Native 依赖,Codegen 会尝试生成其脚手架代码。
创建 android/src/main/java/com/rtncalculator
目录,在这个目录内创建 CalculatorPackage.java
文件
package com.rtncalculator;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.TurboReactPackage;
import java.util.Collections;
import java.util.List;
public class CalculatorPackage extends TurboReactPackage {
@Nullable
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
return null;
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return null;
}
}
ReactPackage
接口的用途是让 React Native 为使用 App 中的 ViewManager
和 Native Modules
,识别出哪些原生类需要在第三方库里导出。
4. 原生代码
最后一步是让您的 Turbo Native Module 准备运行,您需要编写一些让 JavaScript 与原生平台交互的代码。这包含两个主要步骤:
- 运行 Codegen 并查看其生成的代码;
- 编写原生代码,实现 Codegen 生成的接口。
在开发一个使用 Turbo Native Module 的 React Native App 时,将由 App 负责使用 Codegen 生成代码。但在开发 TurboModule 第三方库时,我们需要引用 Codegen 的生成代码,因此查看生成的代码是很有帮助的。
首先,为生成 iOS 和 Android 平台的代码,本指导将向您展示如何手动执行由 Codegen 生成的脚本,以及生成所需要的平台代码。您可以在这里了解到更多关于 Codegen 的内容。
Codegen 生成的代码不该提交到版本管理系统,React Native 会在 App 构建的时候自动生成代码。这是为了确保在 App 内,所有第三方库都正确使用针对某一 React Native 版本的生成代码。
iOS
iOS 的代码生成
为生成 iOS 平台的代码,您需要在 Terminal 执行以下命令:
cd MyApp
yarn add ../RTNCalculator
cd ..
node MyApp/node_modules/react-native/scripts/generate-codegen-artifacts .js \
--path MyApp/ \
--outputPath RTNCalculator/generated/
这脚本首先使用 yarn add
将 RTNCalculator
模块添加到 App。然后通过 generate-codegen-artifacts.js
脚本调用 Codegen。
--path
选项用于声明 App 的路径,--outputPath
选项用于声明 Codegen 生成代码的存放路径。
命令执行后将呈现以下目录文件结构:
generated
└── build
└── generated
└── ios
├── FBReactNativeSpec
│ ├── FBReactNativeSpec-generated.mm
│ └── FBReactNativeSpec.h
├── RCTThirdPartyFabricComponentsProvider.h
├── RCTThirdPartyFabricComponentsProvider.mm
├── RTNCalculatorSpec
│ ├── RTNCalculatorSpec-generated.mm
│ └── RTNCalculatorSpec.h
└── react
└── renderer
└── components
└── rncore
├── ComponentDescriptors.h
├── EventEmitters.cpp
├── EventEmitters.h
├── Props.cpp
├── Props.h
├── RCTComponentViewHelpers.h
├── ShadowNodes.cpp
└── ShadowNodes.h
Turbo Native Module 接口的路径为 generated/build/generated/ios/RTNCalculatorSpec
。
查看 Codegen 章节文档可获得更多文件生成相关细节。
使用 Codegen 生成脚手架代码时,iOS 平台中不会自动清空 build
目录。假如您需要修改接口声明文件的命名,并重新执行了 Codegen,旧的代码会保留下来。如果发生了这种情况,您需要在重新执行 Codegen 之前删除 build
目录。
cd MyApp/ios
rm -rf build