原生平台:原生模块
本指南介绍如何通过为 Windows 平台实现一个 原生模块 来将 Windows 中的非 UI 原生功能公开给 React Native。有关 Windows 上原生开发的高级概述,请在阅读本指南之前参阅 原生平台:概述。
注意: 有关针对 Android 和 iOS 平台实现新原生模块的步骤,请参阅 reactnative.dev 原生模块指南。
架构说明: 本指南遵循创建 Turbo 原生模块 以支持 React Native 新架构的建议。但是,与其他平台不同,适用于 Windows 的 React Native 也直接支持旧架构的 Turbo 原生模块。简而言之,我们在此处创建的 Turbo 原生模块可以被针对任一架构的适用于 Windows 的 React Native 应用使用。有关适用于 Windows 的 React Native 中 React Native 架构的更多信息,请参阅 新架构与旧架构。
高级概述
为了实现原生模块的 Windows 支持,您需要:
- 在 TypeScript 规范文件中定义原生模块的 API 接口
- 使用适用于 Windows 的 React Native 的原生库代码生成器获取 TypeScript 规范文件并为 Windows 代码创建 C++ 头文件
- 编写 Windows C++ 代码以实现生成的头文件中指定的函数
- 在 JavaScript 中使用原生模块
分步指南
0. 设置
您需要一个已初始化并支持 Windows 的 React Native 库项目。
注意: 本指南的其余部分假设您已按照 原生平台:入门 指南设置了一个名为
testlib
的新库项目。
1. 在 TypeScript 中定义 API 接口
新库的默认模板以一个基于项目名称的简单 Turbo 原生模块开始,例如,我们的 testlib
项目包含一个 TestLib
模块,其 API 接口在 src\NativeTestlib.ts
中定义
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
multiply(a: number, b: number): number;
}
export default TurboModuleRegistry.getEnforcing<Spec>('Testlib');
此规范文件声明 React Native 要求每个平台实现一个名为 Testlib
的 Turbo 原生模块,该模块实现一个名为 multiply
的函数,并具有给定的参数和返回类型。
注意: 每个原生模块规范文件都必须命名为
Native<moduleName> + .ts
格式,才能正确识别为规范文件,而不仅仅是库中的常规 TypeScript 文件。
2. 使用适用于 Windows 的 React Native 的原生库代码生成器
现在,在我们为 Turbo 原生模块实现原生 C++ 代码之前,我们需要运行适用于 Windows 的 React Native 的原生库代码生成器,即 codegen-windows 命令,它将获取 TypeScript 规范文件并生成一些带有我们需要实现的 API 接口的 C++ 头文件。
首先,我们需要确保 codegenConfig
对象在我们的库的 package.json
文件中正确定义
"codegenConfig": {
"name": "RNTestlibSpec",
"type": "modules",
"jsSrcsDir": "src",
"outputDir": {
"ios": "ios/generated",
"android": "android/generated"
},
"android": {
"javaPackageName": "com.testlib"
},
"includesGeneratedCode": true,
"windows": {
"namespace": "testlibCodegen",
"outputDirectory": "windows/testlib/codegen",
"separateDataTypes": true
}
}
配置与其他平台部分共享,但对于 Windows,相关字段是 type
、jsSrcsDirs
和 windows
对象。目前我们将保留配置原样,因为它已经设置为支持我们的 Testlib
模块。
注意: 有关配置
codegenConfig
的更多信息,请参阅 codegen-windows 代码生成配置。
我们现在需要做的就是运行 codegen-windows 命令
yarn react-native codegen-windows
注意: 默认情况下,
codegen-windows
命令在每次原生构建开始时自动运行。通过这种方式,TypeScript 规范文件中 API 接口的更改将反映在生成的头文件中,从而强制原生代码与模块所需的 API 接口保持同步。
现在我们应该在项目的代码生成输出目录中看到一些文件,例如我们在配置中指定的 windows\testlib\codegen
文件夹。具体来说,对于我们的 Testlib
模块,我们应该看到一个包含以下内容的 NativeTestlibSpec.g.h
/*
* This file is auto-generated from a NativeModule spec file in js.
*
* This is a C++ Spec class that should be used with MakeTurboModuleProvider to register native modules
* in a way that also verifies at compile time that the native module matches the interface required
* by the TurboModule JS spec.
*/
#pragma once
// clang-format off
#include <NativeModules.h>
#include <tuple>
namespace testlibCodegen {
struct TestlibSpec : winrt::Microsoft::ReactNative::TurboModuleSpec {
static constexpr auto methods = std::tuple{
SyncMethod<double(double, double) noexcept>{0, L"multiply"},
};
template <class TModule>
static constexpr void ValidateModule() noexcept {
constexpr auto methodCheckResults = CheckMethods<TModule, TestlibSpec>();
REACT_SHOW_METHOD_SPEC_ERRORS(
0,
"multiply",
" REACT_SYNC_METHOD(multiply) double multiply(double a, double b) noexcept { /* implementation */ }\n"
" REACT_SYNC_METHOD(multiply) static double multiply(double a, double b) noexcept { /* implementation */ }\n");
}
};
} // namespace testlibCodegen
此文件中 TestlibSpec
类型的目的是,当它包含在我们的原生 C++ 项目中时,如果我们的原生 C++ 实现与模块所需的 API 接口不匹配,则会抛出错误。
3. 实现 Windows C++ 代码
现在,代码生成完成后,终于可以实现 Testlib
模块及其 multiply()
函数了。
3.1 实现 Turbo 原生模块
方便的是,我们的新项目已经包含我们可以查看的实现(对于我们的示例,windows\testlib
文件夹中的 testlib.h
和 testlib.cpp
)
#pragma once
#include "pch.h"
#include "resource.h"
#if __has_include("codegen/NativeTestlibDataTypes.g.h")
#include "codegen/NativeTestlibDataTypes.g.h"
#endif
#include "codegen/NativeTestlibSpec.g.h"
#include "NativeModules.h"
namespace winrt::testlib
{
REACT_MODULE(Testlib)
struct Testlib
{
using ModuleSpec = testlibCodegen::TestlibSpec;
REACT_INIT(Initialize)
void Initialize(React::ReactContext const &reactContext) noexcept;
REACT_SYNC_METHOD(multiply)
double multiply(double a, double b) noexcept;
private:
React::ReactContext m_context;
};
} // namespace winrt::testlib
#include "pch.h"
#include "testlib.h"
namespace winrt::testlib
{
// See https://msdocs.cn/react-native-windows/docs/native-platform for details on writing native modules
void Testlib::Initialize(React::ReactContext const &reactContext) noexcept {
m_context = reactContext;
}
double Testlib::multiply(double a, double b) noexcept {
return a * b;
}
} // namespace winrt::testlib
如您所见,testlib.h
文件定义了一个 Testlib
结构,并带有 REACT_MODULE
属性,以向适用于 Windows 的 React Native 指示此结构包含名为 Testlib
的 Turbo 原生模块的实现。
using ModuleSpec = testlibCodegen::TestlibSpec;
行确保如果 Testlib
结构不符合模块所需的 API 接口,则编译将失败。
注意: 当为具有过时(或缺失)规范文件的原生模块添加 Windows 支持时,删除此行将允许您的原生代码编译,即使它与库的 JavaScript 端预期不匹配。虽然可能,但由于存在 Windows 模块实现与其他平台不同步的风险,因此不建议这样做。
Initialize()
函数带有 REACT_INIT
属性,表示它应该在模块首次创建时运行。查看 testlib.cpp
中的实现,我们看到它保存了一个 ReactContext
,您的模块以后可以使用它来与代码的 JavaScript 端交互(例如,在原生操作期间触发 JavaScript 事件)。
最后,我们还看到 multiply()
函数带有 REACT_SYNC_METHOD
属性,表示该函数既是模块 API 接口的一部分,又是同步函数。查看 testlib.cpp
中的实现,我们看到它做了我们期望的事情,将输入参数相乘并返回结果。
3.2 将 Turbo 原生模块的文件添加到原生项目
默认情况下,原生 Windows 项目(在我们的示例中为 windows\testlib\testlib.vcxproj
和 windows\testlib\testlib.vcxproj.filters
)已经包含 testlib.h
和 testlib.cpp
文件。但是,如果您为模块创建新文件,则需要手动将它们添加到原生 Windows 项目中
<ItemGroup>
<ClInclude Include="testlib.h" />
+ <ClInclude Include="NEWFILE.h" />
<ClInclude Include="ReactPackageProvider.h">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClInclude>
<ClInclude Include="resource.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="testlib.cpp" />
+ <ClCompile Include="NEWFILE.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ReactPackageProvider.cpp">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="testlib.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="NEWFILE.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="testlib.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="NEWFILE.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
注意: 代码生成器在
windows/testlib/codegen
文件夹中创建的所有头文件都已包含在内,无需在此处手动添加。
3.3 向 React 包提供程序注册 Turbo 原生模块
每个适用于 Windows 的 React Native 库都包含一个 IReactPackageProvider
,其中包含库的所有原生模块(和/或组件),以便 React Native 可以在运行时使用它们。
我们需要完成的最后一点原生工作是确保 ReactPackageProvider::CreatePackage
添加 windows\testlib\ReactPackageProvider.cpp
中所有带有属性的模块
#include "pch.h"
#include "ReactPackageProvider.h"
#if __has_include("ReactPackageProvider.g.cpp")
#include "ReactPackageProvider.g.cpp"
#endif
#include "testlib.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::testlib::implementation {
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept {
AddAttributedModules(packageBuilder, true);
}
} // namespace winrt::testlib::implementation
方便的是,我们的新项目已经包含包含 Testlib
Turbo 原生模块的代码。
这里的关键是 #include "testlib.h"
包含和对 AddAttributedModules
函数的调用。此调用确保每个 Turbo 原生模块(即,每个具有 REACT_MODULE
属性的结构)都包含在库的包中。
4. 在 JavaScript 中使用原生模块
现在,如果我们回到 NativeTestlib.ts
TypeScript 规范文件,我们会看到它导出了一个类型,该类型是名为 Testlib
的 Turbo 原生模块,其接口与 Spec
匹配。下一步是在我们的 JavaScript 代码中使用该导出的对象。
由于库的目的是将原生功能公开给库外部的代码(即我们的适用于 Windows 的 React Native 应用代码),因此默认是在项目的索引中导出该功能,在本例中为 src\index.tsx
import Testlib from './NativeTestlib';
export function multiply(a: number, b: number): number {
return Testlib.multiply(a, b);
}
然后我们可以看到,testlib
JavaScript 模块导出一个 multiply()
函数,该函数在内部调用 Testlib.multiply()
函数。
注意: 库不必直接向其使用者公开任何原生模块函数。此示例仅说明了对原生模块函数进行直通调用的最简单情况。库可以而且经常将其原生模块封装在 JavaScript 模块中,因此可能向其客户提供完全不同的 API 接口。
后续步骤
实现原生库后,最后一步是在适用于 Windows 的 React Native 应用中使用它。继续阅读 原生平台:使用原生库。