Przejdź do treści głównej
Wersja: 0.79
Nieoficjalne Tłumaczenie Beta

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++

uwaga

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.

  1. W folderze shared dodaj nowy plik o nazwie Int64.h

  2. Dodaj następujący kod do tego pliku:

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));
}
};

}

Kluczowe elementy twojego niestandardowego nagłówka bridgingu to:

  • Jawna specjalizacja struktury Bridging dla twojego typu. W tym przypadku szablon określa typ int64_t.

  • Funkcja fromJs konwertująca reprezentację JS na C++

  • Funkcja toJs konwertująca reprezentację C++ na JS

uwaga

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.

  1. Otwórz plik specs/NativeSampleTurbomodule

  2. Zmodyfikuj specyfikację w następujący sposób:

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

W tych plikach definiujemy funkcję, która musi zostać zaimplementowana w C++.

3. Implementacja kodu natywnego

Teraz musimy zaimplementować funkcję zadeklarowaną w specyfikacji JS.

  1. Otwórz plik specs/NativeSampleModule.h i wprowadź następujące zmiany:
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. Otwórz plik specs/NativeSampleModule.cpp i zaimplementuj nową funkcję:
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

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.

  1. Otwórz plik App.tsx i wprowadź następujące zmiany:
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. Aby przetestować aplikację na Androidzie, uruchom yarn android z folderu głównego projektu.

  2. Aby przetestować aplikację na iOS, uruchom yarn ios z 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:

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

  1. Otwórz plik specs/NativeSampleModule i dodaj następujące zmiany:
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',
);

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.

  1. Otwórz plik shared/NativeSampleModule.h

  2. Dodaj następujący kod w pliku:

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> {};
// ...
}

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.

uwaga

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.

  1. Otwórz plik shared/NativeSampleModule.h i dodaj definicję funkcji
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. Otwórz plik shared/NativeSampleModule.cpp i dodaj implementację funkcji
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;
}

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ść do jsi::String

  • utf8() konwertuje jsi::String do std::string

  • asNumber() konwertuje właściwość do double

Po ręcznym sparsowaniu obiektu możemy zaimplementować potrzebną logikę.

uwaga

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.

  1. Otwórz plik App.tsx. Usuń zawartość funkcji App().

  2. Zastąp treść funkcji App() następującym kodem:

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>
);

Gratulacje! 🎉

Zmostkowałeś swoje pierwsze typy z JS do C++.