Skip to main content

高级:自定义 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.

  1. shared 文件夹中,添加一个名为 Int64.h 的新文件

    ¥In the shared folder, add a new file called Int64.h

  2. 将以下代码添加到该文件:

    ¥Add the following code to that file:

Int64.h
#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 the int64_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.

  1. 打开 specs/NativeSampleTurbomodule

    ¥Open the specs/NativeSampleTurbomodule

  2. 修改规范如下:

    ¥Modify the spec as follows:

NativeSampleModule.ts
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',
);

在此文件中,我们定义需要在 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.

  1. 打开 specs/NativeSampleModule.h 文件并应用以下更改:

    ¥Open the specs/NativeSampleModule.h file and apply the following changes:

NativeSampleModule.h
#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

  1. 打开 specs/NativeSampleModule.cpp 文件并应用实现新函数:

    ¥Open the specs/NativeSampleModule.cpp file and apply the implement the new function:

NativeSampleModule.cpp
#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.

  1. 打开 App.tsx 代码应用以下更改:

    ¥Open the App.tsx code apply the following changes:

App.tsx
// ...
+ 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>
);
}
//...
  1. 要在 Android 上测试应用,请从项目的根文件夹运行 yarn android

    ¥To test the app on Android, run yarn android from the root folder of your project.

  2. 要在 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.

  1. 打开 specs/NativeSampleModule 文件并添加以下更改。

    ¥Open the specs/NativeSampleModule file and add the following changes.

NativeSampleModule (Add Address type and validateAddress function)
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',
);

此代码定义了新的 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 将生成两种辅助类型:NativeSampleModuleAddressNativeSampleModuleAddressBridging

¥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.

  1. 打开 shared/NativeSampleModule.h 文件

    ¥Open the shared/NativeSampleModule.h file

  2. 在文件中添加以下代码:

    ¥Add the following code in the file:

NativeSampleModule.h (Bridging the Address type)
#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.

  1. 打开 shared/NativeSampleModule.h 文件并添加函数定义

    ¥Open the shared/NativeSampleModule.h file and add the function definition

NativeSampleModule.h (validateAddress function prototype)
  std::string reverseString(jsi::Runtime& rt, std::string input);

+ bool validateAddress(jsi::Runtime &rt, jsi::Object input);
};

} // namespace facebook::react
  1. 打开 shared/NativeSampleModule.cpp 文件并添加函数实现

    ¥Open the shared/NativeSampleModule.cpp file and add the function implementation

NativeSampleModule.cpp (validateAddress 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 to jsi::String.

  • utf8()jsi::String 转换为 std::string

    ¥utf8() converts the jsi::String to a std::string.

  • asNumber() 将属性转换为 double

    ¥asNumber() converts the property to a double.

手动解析对象后,我们就可以实现所需的逻辑。

¥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.

  1. 打开 App.tsx 文件。删除 App() 函数的内容。

    ¥Open the App.tsx file. Remove the content of the App() function.

  2. 用以下代码替换 App() 函数的主体:

    ¥Replace the body of the App() function with the following code:

App.tsx (App function body replacement)
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++.