高级:自定义 C++ 类型
¥Advanced: Custom C++ Types
本指南假设你熟悉 纯 C++ Turbo Native 模块 指南。这将在该指南的基础上构建。
¥This guide assumes that you are familiar with the Pure C++ Turbo Native Modules guide. This will build on top of that guide.
C++ Turbo Native 模块支持大多数 std::
标准类型的 桥接功能。你可以在模块中使用大多数这些类型,而无需任何额外的代码。
¥C++ Turbo Native Modules support bridging functionality for most std::
standard types. You can use most of those types in your modules without any additional code required.
如果你想在应用或库中添加对新类型和自定义类型的支持,则需要提供必要的 bridging
头文件。
¥If you want to add support for new and custom types in your app or library, you need to provide the necessary bridging
header file.
添加新的自定义:Int64
¥Adding a New Custom: Int64
C++ Turbo Native 模块尚不支持 int64_t
数字 - 因为 JavaScript 不支持大于 2^53 的数字。为了表示大于 2^53 的数字,我们可以在 JS 中使用 string
类型,并在 C++ 中自动将其转换为 int64_t
。
¥C++ Turbo Native Modules don't support int64_t
numbers yet - because JavaScript doesn't support numbers greater 2^53. To represent numbers greater than 2^53, we can use a string
type in JS and automatically convert it to int64_t
in C++.
1. 创建桥接头文件
¥ Create the Bridging Header file
支持新自定义类型的第一步是定义桥接头,负责将类型从 JS 表示转换为 C++ 表示,并从 C++ 表示转换为 JS 表示。
¥The first step to support a new custom type is to define the bridging header that takes care of converting the type from the JS representation to the C++ representation, and from the C++ representation to the JS one.
-
在
shared
文件夹中,添加一个名为Int64.h
的新文件¥In the
shared
folder, add a new file calledInt64.h
-
将以下代码添加到该文件:
¥Add the following code to that file:
#pragma once
#include <react/bridging/Bridging.h>
namespace facebook::react {
template <>
struct Bridging<int64_t> {
// Converts from the JS representation to the C++ representation
static int64_t fromJs(jsi::Runtime &rt, const jsi::String &value) {
try {
size_t pos;
auto str = value.utf8(rt);
auto num = std::stoll(str, &pos);
if (pos != str.size()) {
throw std::invalid_argument("Invalid number"); // don't support alphanumeric strings
}
return num;
} catch (const std::logic_error &e) {
throw jsi::JSError(rt, e.what());
}
}
// Converts from the C++ representation to the JS representation
static jsi::String toJs(jsi::Runtime &rt, int64_t value) {
return bridging::toJs(rt, std::to_string(value));
}
};
}
自定义桥接头文件的关键组件是:
¥The key components for your custom bridging header are:
-
为你的自定义类型显式专门化
Bridging
结构。在这种情况下,模板指定int64_t
类型。¥Explicit specialization of the
Bridging
struct for your custom type. In this case, the template specify theint64_t
type. -
用于从 JS 表示转换为 C++ 表示的
fromJs
函数¥A
fromJs
function to convert from the JS representation to the C++ representation -
用于从 C++ 表示转换为 JS 表示的
toJs
函数¥A
toJs
function to convert from the C++ representation to the JS representation
在 iOS 上,请记住将 Int64.h
文件添加到 Xcode 项目中。
¥On iOS, remember to add the Int64.h
file to the Xcode project.
2. 修改 JS 规范
¥ Modify the JS Spec
现在,我们可以修改 JS 规范以添加使用新类型的方法。与往常一样,我们可以将 Flow 或 TypeScript 用于我们的规范。
¥Now, we can modify the JS spec to add a method that uses the new type. As usual, we can use either Flow or TypeScript for our specs.
-
打开
specs/NativeSampleTurbomodule
¥Open the
specs/NativeSampleTurbomodule
-
修改规范如下:
¥Modify the spec as follows:
- TypeScript
- Flow
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
+ readonly cubicRoot: (input: string) => number;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
// @flow
import type {TurboModule} from 'react-native';
import { TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
+ +cubicRoot: (input: string) => number;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
"NativeSampleModule"
): Spec);
在此文件中,我们定义需要在 C++ 中实现的函数。
¥In this files, we are defining the function that needs to be implemented in C++.
3. 实现原生代码
¥ Implement the Native Code
现在,我们需要实现我们在 JS 规范中声明的函数。
¥Now, we need to implement the function that we declared in the JS specification.
-
打开
specs/NativeSampleModule.h
文件并应用以下更改:¥Open the
specs/NativeSampleModule.h
file and apply the following changes:
#pragma once
#include <AppSpecsJSI.h>
#include <memory>
#include <string>
+ #include "Int64.h"
namespace facebook::react {
class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);
std::string reverseString(jsi::Runtime& rt, std::string input);
+ int32_t cubicRoot(jsi::Runtime& rt, int64_t input);
};
} // namespace facebook::react
-
打开
specs/NativeSampleModule.cpp
文件并应用实现新函数:¥Open the
specs/NativeSampleModule.cpp
file and apply the implement the new function:
#include "NativeSampleModule.h"
+ #include <cmath>
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());
}
+int32_t NativeSampleModule::cubicRoot(jsi::Runtime& rt, int64_t input) {
+ return std::cbrt(input);
+}
} // namespace facebook::react
实现导入 <cmath>
C++ 库来执行数学运算,然后使用 <cmath>
模块中的 cbrt
原语实现 cubicRoot
函数。
¥The implementation imports the <cmath>
C++ library to perform mathematical operations, then it implements the cubicRoot
function using the cbrt
primitive from the <cmath>
module.
4. 在你的应用中测试你的代码
¥ Test your code in Your App
现在,我们可以在我们的应用中测试代码。
¥Now, we can test the code in our app.
首先,我们需要更新 App.tsx
文件以使用 TurboModule 中的新方法。然后,我们可以在 Android 和 iOS 中构建我们的应用。
¥First, we need to update the App.tsx
file to use the new method from the TurboModule. Then, we can build our apps in Android and iOS.
-
打开
App.tsx
代码应用以下更改:¥Open the
App.tsx
code apply the following changes:
// ...
+ const [cubicSource, setCubicSource] = React.useState('')
+ const [cubicRoot, setCubicRoot] = React.useState(0)
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C++ Turbo Native Module Example
</Text>
<Text>Write down here the text you want to revert</Text>
<TextInput
style={styles.textInput}
placeholder="Write your text here"
onChangeText={setValue}
value={value}
/>
<Button title="Reverse" onPress={onPress} />
<Text>Reversed text: {reversedValue}</Text>
+ <Text>For which number do you want to compute the Cubic Root?</Text>
+ <TextInput
+ style={styles.textInput}
+ placeholder="Write your text here"
+ onChangeText={setCubicSource}
+ value={cubicSource}
+ />
+ <Button title="Get Cubic Root" onPress={() => setCubicRoot(SampleTurboModule.cubicRoot(cubicSource))} />
+ <Text>The cubic root is: {cubicRoot}</Text>
</View>
</SafeAreaView>
);
}
//...
-
要在 Android 上测试应用,请从项目的根文件夹运行
yarn android
。¥To test the app on Android, run
yarn android
from the root folder of your project. -
要在 iOS 上测试应用,请从项目的根文件夹运行
yarn ios
。¥To test the app on iOS, run
yarn ios
from the root folder of your project.
添加新的结构化自定义类型:地址
¥Adding a New Structured Custom Type: Address
上述方法可以推广到任何类型。对于结构化类型,React Native 提供了一些辅助函数,可以更轻松地将它们从 JS 桥接到 C++ 以及反之亦然。
¥The approach above can be generalized to any kind of type. For structured types, React Native provides some helper functions that make it easier to bridge them from JS to C++ and vice versa.
假设我们想要桥接具有以下属性的自定义 Address
类型:
¥Let's assume that we want to bridge a custom Address
type with the following properties:
interface Address {
street: string;
num: number;
isInUS: boolean;
}
1. 在规范中定义类型
¥ Define the type in the specs
对于第一步,让我们在 JS 规范中定义新的自定义类型,以便 Codegen 可以输出所有支持代码。这样,我们就不用手动写代码了。
¥For the first step, let's define the new custom type in the JS specs, so that Codegen can output all the supporting code. In this way, we don't have to manually write the code.
-
打开
specs/NativeSampleModule
文件并添加以下更改。¥Open the
specs/NativeSampleModule
file and add the following changes.
- TypeScript
- Flow
import {TurboModule, TurboModuleRegistry} from 'react-native';
+export type Address = {
+ street: string,
+ num: number,
+ isInUS: boolean,
+};
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
+ readonly validateAddress: (input: Address) => boolean;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
// @flow
import type {TurboModule} from 'react-native';
import { TurboModuleRegistry } from "react-native";
+export type Address = {
+ street: string,
+ num: number,
+ isInUS: boolean,
+};
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
+ +validateAddress: (input: Address) => boolean;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
"NativeSampleModule"
): Spec);
此代码定义了新的 Address
类型并为 Turbo Native 模块定义了一个新的 validateAddress
函数。请注意,validateFunction
需要 Address
对象作为参数。
¥This code defines the new Address
type and defines a new validateAddress
function for the Turbo Native Module. Notice that the validateFunction
requires an Address
object as parameter.
也可以有返回自定义类型的函数。
¥It is also possible to have functions that return custom types.
2. 定义桥接代码
¥ Define the bridging code
从规范中定义的 Address
类型,Codegen 将生成两种辅助类型:NativeSampleModuleAddress
和 NativeSampleModuleAddressBridging
。
¥From the Address
type defined in the specs, Codegen will generate two helper types: NativeSampleModuleAddress
and NativeSampleModuleAddressBridging
.
第一种类型是 Address
的定义。第二种类型包含将自定义类型从 JS 桥接到 C++ 以及反之亦然的所有基础结构。我们需要添加的唯一额外步骤是定义扩展 NativeSampleModuleAddressBridging
类型的 Bridging
结构。
¥The first type is the definition of the Address
. The second type contains all the infrastructure to bridge the custom type from JS to C++ and vice versa. The only extra step we need to add is to define the Bridging
structure that extends the NativeSampleModuleAddressBridging
type.
-
打开
shared/NativeSampleModule.h
文件¥Open the
shared/NativeSampleModule.h
file -
在文件中添加以下代码:
¥Add the following code in the file:
#include "Int64.h"
#include <memory>
#include <string>
namespace facebook::react {
+ using Address = NativeSampleModuleAddress<std::string, int32_t, bool>;
+ template <>
+ struct Bridging<Address>
+ : NativeSampleModuleAddressBridging<Address> {};
// ...
}
此代码为通用类型 NativeSampleModuleAddress
定义了一个 Address
类型别名。泛型的顺序很重要:第一个模板参数指的是结构的第一个数据类型,第二个指的是第二个,依此类推。
¥This code defines an Address
typealias for the generic type NativeSampleModuleAddress
. The order of the generics matters: the first template argument refers to the first data type of the struct, the second refers to the second, and so forth.
然后,代码通过扩展 Codegen 生成的 NativeSampleModuleAddressBridging
,为新的 Address
类型添加 Bridging
特化。
¥Then, the code adds the Bridging
specialization for the new Address
type, by extending NativeSampleModuleAddressBridging
that is generated by Codegen.
生成此类型时要遵循一个约定:
¥There is a convention that is followed to generate this types:
-
名称的第一部分始终是模块的类型。
NativeSampleModule
,在此示例中。¥The first part of the name is always the type of the module.
NativeSampleModule
, in this example. -
名称的第二部分始终是规范中定义的 JS 类型的名称。
Address
,在此示例中。:::¥The second part of the name is always the name of the JS type defined in the specs.
Address
, in this example.
3. 实现原生代码
¥ Implement the Native Code
现在,我们需要用 C++ 实现 validateAddress
函数。首先,我们需要将函数声明添加到 .h
文件中,然后我们可以在 .cpp
文件中实现它。
¥Now, we need to implement the validateAddress
function in C++. First, we need to add the function declaration into the .h
file, and then we can implement it in the .cpp
file.
-
打开
shared/NativeSampleModule.h
文件并添加函数定义¥Open the
shared/NativeSampleModule.h
file and add the function definition
std::string reverseString(jsi::Runtime& rt, std::string input);
+ bool validateAddress(jsi::Runtime &rt, jsi::Object input);
};
} // namespace facebook::react
-
打开
shared/NativeSampleModule.cpp
文件并添加函数实现¥Open the
shared/NativeSampleModule.cpp
file and add the function implementation
bool NativeSampleModule::validateAddress(jsi::Runtime &rt, jsi::Object input) {
std::string street = input.getProperty(rt, "street").asString(rt).utf8(rt);
int32_t number = input.getProperty(rt, "num").asNumber();
return !street.empty() && number > 0;
}
在实现中,代表 Address
的对象是 jsi::Object
。要从此对象中提取值,我们需要使用 JSI
提供的访问器:
¥In the implementation, the object that represents the Address
is a jsi::Object
. To extract the values from this object, we need to use the accessors provided by JSI
:
-
getProperty()
通过名称从对象中检索属性。¥
getProperty()
retrieves the property from and object by name. -
asString()
将属性转换为jsi::String
。¥
asString()
converts the property tojsi::String
. -
utf8()
将jsi::String
转换为std::string
。¥
utf8()
converts thejsi::String
to astd::string
. -
asNumber()
将属性转换为double
。¥
asNumber()
converts the property to adouble
.
手动解析对象后,我们就可以实现所需的逻辑。
¥Once we manually parsed the object, we can implement the logic that we need.
如果你想了解有关 JSI
及其工作原理的更多信息,请查看 App.JS 2024 中的 精彩演讲
¥If you want to learn more about JSI
and how it works, have a look at this great talk from App.JS 2024
4. 在应用中测试代码
¥ Testing the code in the app
要在应用中测试代码,我们必须修改 App.tsx
文件。
¥To test the code in the app, we have to modify the App.tsx
file.
-
打开
App.tsx
文件。删除App()
函数的内容。¥Open the
App.tsx
file. Remove the content of theApp()
function. -
用以下代码替换
App()
函数的主体:¥Replace the body of the
App()
function with the following code:
const [street, setStreet] = React.useState('');
const [num, setNum] = React.useState('');
const [isValidAddress, setIsValidAddress] = React.useState<
boolean | null
>(null);
const onPress = () => {
let houseNum = parseInt(num, 10);
if (isNaN(houseNum)) {
houseNum = -1;
}
const address = {
street,
num: houseNum,
isInUS: false,
};
const result = SampleTurboModule.validateAddress(address);
setIsValidAddress(result);
};
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C Turbo Native Module Example
</Text>
<Text>Address:</Text>
<TextInput
style={styles.textInput}
placeholder="Write your address here"
onChangeText={setStreet}
value={street}
/>
<Text>Number:</Text>
<TextInput
style={styles.textInput}
placeholder="Write your address here"
onChangeText={setNum}
value={num}
/>
<Button title="Validate" onPress={onPress} />
{isValidAddress != null && (
<Text>
Your address is {isValidAddress ? 'valid' : 'not valid'}
</Text>
)}
</View>
</SafeAreaView>
);
恭喜!🎉
¥Congratulation! 🎉
你将第一种类型从 JS 桥接到 C++。
¥You bridged your first types from JS to C++.