跳到主要內容

進階:自訂 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 表示形式。

  1. shared 資料夾中,新增一個名為 Int64.h 的新檔案
  2. 將以下程式碼新增到該檔案
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。

  1. 開啟 specs/NativeSampleTurbomodule
  2. 如下修改 spec
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++ 中實作的函數。

3. 實作原生程式碼

現在,我們需要實作在 JS 規範中宣告的函數。

  1. 開啟 specs/NativeSampleModule.h 檔案並套用以下變更
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 檔案並實作新函數
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 中建置我們的應用程式。

  1. 開啟 App.tsx 程式碼並套用以下變更
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
  2. 若要在 iOS 上測試應用程式,請從專案的根資料夾執行 yarn ios

新增新的結構化自訂類型:Address

上述方法可以推廣到任何類型的類型。對於結構化類型,React Native 提供了一些輔助函數,使從 JS 到 C++ 以及從 C++ 到 JS 的橋接更容易。

假設我們想要橋接具有以下屬性的自訂 Address 類型

ts
interface Address {
street: string;
num: number;
isInUS: boolean;
}

1. 在 specs 中定義類型

對於第一步,讓我們在 JS specs 中定義新的自訂類型,以便 Codegen 可以輸出所有支援程式碼。這樣,我們就不必手動編寫程式碼。

  1. 開啟 specs/NativeSampleModule 檔案並新增以下變更。
NativeSampleModule (新增 Address 類型和 validateAddress 函數)
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 原生模組定義了新的 validateAddress 函數。請注意,validateFunction 需要一個 Address 物件作為參數。

也可以讓函數傳回自訂類型。

2. 定義橋接程式碼

從 specs 中定義的 Address 類型,Codegen 將產生兩個輔助類型:NativeSampleModuleAddressNativeSampleModuleAddressBridging

第一種類型是 Address 的定義。第二種類型包含將自訂類型從 JS 橋接到 C++ 以及從 C++ 橋接到 JS 的所有基礎架構。我們需要新增的唯一額外步驟是定義擴展 NativeSampleModuleAddressBridging 類型的 Bridging 結構。

  1. 開啟 shared/NativeSampleModule.h 檔案
  2. 在檔案中新增以下程式碼
NativeSampleModule.h (橋接 Address 類型)
#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 檔案中實作它。

  1. 開啟 shared/NativeSampleModule.h 檔案並新增函數定義
NativeSampleModule.h (validateAddress 函數原型)
  std::string reverseString(jsi::Runtime& rt, std::string input);

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

} // namespace facebook::react
  1. 開啟 shared/NativeSampleModule.cpp 檔案並新增函數實作
NativeSampleModule.cpp (validateAddress 實作)
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 檔案。

  1. 開啟 App.tsx 檔案。移除 App() 函數的內容。
  2. App() 函數的主體替換為以下程式碼
App.tsx (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++。