上級: カスタム 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
型を指定します。 - JS 表現から C++ 表現に変換する
fromJs
関数 - C++ 表現から JS 表現に変換する
toJs
関数
iOSでは、Int64.h
ファイルをXcodeプロジェクトに追加することを忘れないでください。
2. JS Specの変更
これで、新しい型を使用するメソッドを追加するために JS の仕様を変更できます。通常どおり、スペックには Flow または TypeScript のどちらかを使用できます。
specs/NativeSampleTurbomodule
を開きます。- 以下のように仕様を変更します。
- 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++ 間でブリッジングを容易にするヘルパー関数を提供します。
次のプロパティを持つカスタム Address
型をブリッジングしたいと仮定しましょう。
interface Address {
street: string;
num: number;
isInUS: boolean;
}
1. スペックでの型の定義
最初のステップとして、JS の仕様で新しいカスタム型を定義し、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 Native Module の新しい validateAddress
関数を定義します。validateFunction
は Address
オブジェクトをパラメータとして必要とすることに注意してください。
カスタム型を返す関数を持つことも可能です。
2. ブリッジングコードの定義
スペックで定義された Address
型から、Codegen は NativeSampleModuleAddress
と NativeSampleModuleAddressBridging
の 2 つのヘルパー型を生成します。
最初の型は Address
の定義です。2 番目の型は、カスタム型を JS から C++ へ、またはその逆にブリッジするためのすべてのインフラストラクチャを含みます。追加で必要な手順は、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
型エイリアスを定義します。ジェネリクスの順序は重要です。最初のテンプレート引数は構造体の最初のデータ型を指し、2 番目の引数は 2 番目のデータ型を指します。
次に、Codegenによって生成されるNativeSampleModuleAddressBridging
を拡張することで、新しいAddress
型にBridging
特殊化を追加します。
これらの型を生成するために従うべき規則があります。
- 名前の最初の部分は常にモジュールの型です。この例では
NativeSampleModule
です。 - 名前の2番目の部分は常に、仕様で定義された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++ への最初の型をブリッジしました。