進階:自訂 C++ 類型
本指南假設您已熟悉純 C++ Turbo 原生模組指南。本指南將以此為基礎進行擴展。
C++ Turbo 原生模組支援大多數 std::
標準類型的橋接功能。您可以在模組中使用大多數這些類型,而無需任何額外程式碼。
如果您想在您的應用程式或函式庫中新增對新的和自訂類型的支援,您需要提供必要的 bridging
標頭檔。
新增新的自訂類型:Int64
C++ Turbo 原生模組尚不支援 int64_t
數字 - 因為 JavaScript 不支援大於 2^53 的數字。為了表示大於 2^53 的數字,我們可以在 JS 中使用 string
類型,並在 C++ 中自動將其轉換為 int64_t
。
1. 建立橋接標頭檔
支援新的自訂類型的第一步是定義橋接標頭,該標頭負責將類型從 JS 表示形式轉換為 C++ 表示形式,以及從 C++ 表示形式轉換為 JS 表示形式。
- 在
shared
資料夾中,新增一個名為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));
}
};
}
您的自訂橋接標頭的關鍵組件是
- 您的自訂類型的
Bridging
結構的顯式特化。在本例中,模板指定了int64_t
類型。 - 一個
fromJs
函數,用於從 JS 表示形式轉換為 C++ 表示形式 - 一個
toJs
函數,用於從 C++ 表示形式轉換為 JS 表示形式
在 iOS 上,記得將 Int64.h
檔案新增到 Xcode 專案中。
2. 修改 JS Spec
現在,我們可以修改 JS spec 以新增使用新類型的方法。與往常一樣,我們可以對 spec 使用 Flow 或 TypeScript。
- 開啟
specs/NativeSampleTurbomodule
- 如下修改 spec
- 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++ 中實作的函數。
3. 實作原生程式碼
現在,我們需要實作在 JS 規範中宣告的函數。
- 開啟
specs/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
- 開啟
specs/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
函數。
4. 在您的應用程式中測試您的程式碼
現在,我們可以在我們的應用程式中測試程式碼。
首先,我們需要更新 App.tsx
檔案以使用來自 TurboModule 的新方法。然後,我們可以在 Android 和 iOS 中建置我們的應用程式。
- 開啟
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>
);
}
//...
- 若要在 Android 上測試應用程式,請從專案的根資料夾執行
yarn android
。 - 若要在 iOS 上測試應用程式,請從專案的根資料夾執行
yarn ios
。
新增新的結構化自訂類型:Address
上述方法可以推廣到任何類型的類型。對於結構化類型,React Native 提供了一些輔助函數,使從 JS 到 C++ 以及從 C++ 到 JS 的橋接更容易。
假設我們想要橋接具有以下屬性的自訂 Address
類型
interface Address {
street: string;
num: number;
isInUS: boolean;
}
1. 在 specs 中定義類型
對於第一步,讓我們在 JS specs 中定義新的自訂類型,以便 Codegen 可以輸出所有支援程式碼。這樣,我們就不必手動編寫程式碼。
- 開啟
specs/NativeSampleModule
檔案並新增以下變更。
- 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 原生模組定義了新的 validateAddress
函數。請注意,validateFunction
需要一個 Address
物件作為參數。
也可以讓函數傳回自訂類型。
2. 定義橋接程式碼
從 specs 中定義的 Address
類型,Codegen 將產生兩個輔助類型:NativeSampleModuleAddress
和 NativeSampleModuleAddressBridging
。
第一種類型是 Address
的定義。第二種類型包含將自訂類型從 JS 橋接到 C++ 以及從 C++ 橋接到 JS 的所有基礎架構。我們需要新增的唯一額外步驟是定義擴展 NativeSampleModuleAddressBridging
類型的 Bridging
結構。
- 開啟
shared/NativeSampleModule.h
檔案 - 在檔案中新增以下程式碼
#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
類型別名。泛型的順序很重要:第一個模板引數指的是結構的第一個資料類型,第二個指的是第二個,依此類推。
然後,程式碼透過擴展由 Codegen 產生的 NativeSampleModuleAddressBridging
,為新的 Address
類型新增 Bridging
特化。
有一個慣例會遵循以產生這些類型
- 名稱的第一部分始終是模組的類型。在本例中為
NativeSampleModule
。 - 名稱的第二部分始終是在 specs 中定義的 JS 類型的名稱。在本例中為
Address
。
3. 實作原生程式碼
現在,我們需要在 C++ 中實作 validateAddress
函數。首先,我們需要將函數宣告新增到 .h
檔案中,然後我們可以在 .cpp
檔案中實作它。
- 開啟
shared/NativeSampleModule.h
檔案並新增函數定義
std::string reverseString(jsi::Runtime& rt, std::string input);
+ bool validateAddress(jsi::Runtime &rt, jsi::Object input);
};
} // namespace facebook::react
- 開啟
shared/NativeSampleModule.cpp
檔案並新增函數實作
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
提供的存取器
getProperty()
按名稱從物件中檢索屬性。asString()
將屬性轉換為jsi::String
。utf8()
將jsi::String
轉換為std::string
。asNumber()
將屬性轉換為double
。
一旦我們手動解析了物件,我們就可以實作我們需要的邏輯。
如果您想了解更多關於 JSI
及其運作方式的資訊,請觀看 App.JS 2024 的這個精彩演講
4. 在應用程式中測試程式碼
若要在應用程式中測試程式碼,我們必須修改 App.tsx
檔案。
- 開啟
App.tsx
檔案。移除App()
函數的內容。 - 將
App()
函數的主體替換為以下程式碼
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>
);
恭喜! 🎉
您已將您的第一個類型從 JS 橋接到 C++。