Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →
Zaawansowane: Niestandardowe typy C++
Ten przewodnik zakłada, że znasz tematykę Modułów Turbo Native w czystym C++. Będziemy rozwijać te zagadnienia.
Moduły Turbo Native w C++ obsługują funkcjonalność bridgingu dla większości typów standardowych std::. Możesz używać tych typów w modułach bez dodatkowego kodu.
Jeśli chcesz dodać obsługę nowych, niestandardowych typów w swojej aplikacji lub bibliotece, musisz dostarczyć odpowiedni plik nagłówkowy bridging.
Dodawanie nowego typu niestandardowego: Int64
Moduły Turbo Native w C++ nie obsługują jeszcze liczb int64_t — ponieważ JavaScript nie obsługuje liczb większych niż 2^53. Aby reprezentować większe wartości, możemy użyć typu string w JS i automatycznie konwertować go na int64_t w C++.
1. Tworzenie pliku nagłówkowego bridgingu
Pierwszym krokiem do obsługi nowego typu jest zdefiniowanie nagłówka bridgingu, który konwertuje typ z reprezentacji JS na reprezentację C++ oraz z reprezentacji C++ na reprezentację JS.
-
W folderze
shareddodaj nowy plik o nazwieInt64.h -
Dodaj następujący kod do tego pliku:
#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));
}
};
}
Kluczowe elementy twojego niestandardowego nagłówka bridgingu to:
-
Jawna specjalizacja struktury
Bridgingdla twojego typu. W tym przypadku szablon określa typint64_t. -
Funkcja
fromJskonwertująca reprezentację JS na C++ -
Funkcja
toJskonwertująca reprezentację C++ na JS
Na iOS pamiętaj o dodaniu pliku Int64.h do projektu w Xcode.
2. Modyfikacja specyfikacji JS
Teraz możemy zmodyfikować specyfikację JS, aby dodać metodę wykorzystującą nowy typ. Jak zwykle możemy użyć Flow lub TypeScript.
-
Otwórz plik
specs/NativeSampleTurbomodule -
Zmodyfikuj specyfikację w następujący sposób:
- 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);
W tych plikach definiujemy funkcję, która musi zostać zaimplementowana w C++.
3. Implementacja kodu natywnego
Teraz musimy zaimplementować funkcję zadeklarowaną w specyfikacji JS.
- Otwórz plik
specs/NativeSampleModule.hi wprowadź następujące zmiany:
#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
- Otwórz plik
specs/NativeSampleModule.cppi zaimplementuj nową funkcję:
#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
Implementacja importuje bibliotekę <cmath> do operacji matematycznych, a następnie implementuje funkcję cubicRoot używającą prymitywu cbrt z modułu <cmath>.
4. Testowanie kodu w aplikacji
Teraz możemy przetestować kod w naszej aplikacji.
Najpierw zaktualizuj plik App.tsx, aby używał nowej metody z TurboModule. Następnie zbuduj aplikacje na Androida i iOS.
- Otwórz plik
App.tsxi wprowadź następujące zmiany:
// ...
+ 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>
);
}
//...
-
Aby przetestować aplikację na Androidzie, uruchom
yarn androidz folderu głównego projektu. -
Aby przetestować aplikację na iOS, uruchom
yarn iosz folderu głównego projektu.
Dodawanie nowego strukturalnego typu niestandardowego: Adres
Powyższe podejście można uogólnić na dowolny typ danych. Dla typów strukturalnych React Native udostępnia pomocnicze funkcje, które ułatwiają ich mostkowanie między JavaScript a C++.
Załóżmy, że chcemy mostkować niestandardowy typ Address o następujących właściwościach:
interface Address {
street: string;
num: number;
isInUS: boolean;
}
1. Definicja typu w specyfikacjach
Najpierw zdefiniujmy nowy niestandardowy typ w specyfikacjach JS, aby Codegen mógł wygenerować wspierający kod. Dzięki temu nie musimy pisać kodu ręcznie.
- Otwórz plik
specs/NativeSampleModulei dodaj następujące zmiany:
- 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);
Ten kod definiuje nowy typ Address oraz deklaruje nową funkcję validateAddress dla modułu Turbo Native. Zauważ, że validateFunction przyjmuje obiekt typu Address jako parametr.
Możliwe jest również tworzenie funkcji zwracających niestandardowe typy.
2. Definicja kodu mostkującego
Na podstawie typu Address zdefiniowanego w specyfikacjach, Codegen wygeneruje dwa pomocnicze typy: NativeSampleModuleAddress i NativeSampleModuleAddressBridging.
Pierwszy typ to definicja struktury Address. Drugi typ zawiera infrastrukturę do mostkowania niestandardowego typu między JS a C++. Jedyne, co musimy dodatkowo zrobić, to zdefiniować strukturę Bridging rozszerzającą typ NativeSampleModuleAddressBridging.
-
Otwórz plik
shared/NativeSampleModule.h -
Dodaj następujący kod w pliku:
#include "Int64.h"
#include <memory>
#include <string>
namespace facebook::react {
+ using Address = NativeSampleModuleAddress<std::string, int32_t, bool>;
+ template <>
+ struct Bridging<Address>
+ : NativeSampleModuleAddressBridging<Address> {};
// ...
}
Ten kod definiuje alias typu Address dla generycznego typu NativeSampleModuleAddress. Kolejność argumentów generycznych ma znaczenie: pierwszy argument szablonu odnosi się do pierwszego typu danych w strukturze, drugi do następnego itd.
Następnie kod dodaje specjalizację Bridging dla nowego typu Address, rozszerzając NativeSampleModuleAddressBridging wygenerowane przez Codegen.
Obowiązuje konwencja nazewnictwa tych typów:
- Pierwsza część nazwy to zawsze nazwa modułu, np.
NativeSampleModule - Druga część to nazwa typu JS zdefiniowanego w specyfikacjach, np.
Address
3. Implementacja kodu natywnego
Teraz musimy zaimplementować funkcję validateAddress w C++. Najpierw dodajemy deklarację funkcji w pliku .h, a następnie implementację w pliku .cpp.
- Otwórz plik
shared/NativeSampleModule.hi dodaj definicję funkcji
std::string reverseString(jsi::Runtime& rt, std::string input);
+ bool validateAddress(jsi::Runtime &rt, jsi::Object input);
};
} // namespace facebook::react
- Otwórz plik
shared/NativeSampleModule.cppi dodaj implementację funkcji
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;
}
W implementacji obiekt reprezentujący Address to jsi::Object. Aby wyodrębnić wartości z tego obiektu, używamy akcesorów dostarczanych przez JSI:
-
getProperty()pobiera właściwość z obiektu po nazwie -
asString()konwertuje właściwość dojsi::String -
utf8()konwertujejsi::Stringdostd::string -
asNumber()konwertuje właściwość dodouble
Po ręcznym sparsowaniu obiektu możemy zaimplementować potrzebną logikę.
Jeśli chcesz dowiedzieć się więcej o JSI i jego działaniu, obejrzyj ten doskonały wykład z App.JS 2024
4. Testowanie kodu w aplikacji
Aby przetestować kod w aplikacji, musimy zmodyfikować plik App.tsx.
-
Otwórz plik
App.tsx. Usuń zawartość funkcjiApp(). -
Zastąp treść funkcji
App()następującym kodem:
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>
);
Gratulacje! 🎉
Zmostkowałeś swoje pierwsze typy z JS do C++.