メインコンテンツへスキップ

上級者向け: カスタムC++型

このガイドは、Pure C++ Turbo Native Modulesガイドに精通していることを前提としています。このガイドはその上に構築されます。

C++ Turbo Native Modulesは、ほとんどのstd::標準型に対してブリッジ機能をサポートしています。追加のコードを必要とせずに、これらの型のほとんどをモジュールで使用できます。

アプリやライブラリで新しいカスタム型のサポートを追加したい場合は、必要なbridgingヘッダーファイルを提供する必要があります。

新しいカスタム型の追加: Int64

C++ Turbo Native Modulesは、まだ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型を指定します。
  • JS表現からC++表現に変換するためのfromJs関数
  • C++表現からJS表現に変換するためのtoJs関数

iOSでは、Int64.hファイルをXcodeプロジェクトに追加することを忘れないでください。

2. JS Specの変更

次に、JS specを変更して、新しい型を使用するメソッドを追加できます。通常通り、スペックにはFlowまたはTypeScriptのいずれかを使用できます。

  1. specs/NativeSampleTurbomoduleを開きます。
  2. スペックを次のように変更します。
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++へ、またその逆へのブリッジングを容易にするヘルパー関数を提供しています。

以下のプロパティを持つカスタムAddress型をブリッジしたいと仮定しましょう。

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

1. specsでの型の定義

最初のステップとして、Codegenがすべてのサポートコードを出力できるように、JS specsで新しいカスタム型を定義しましょう。これにより、手動でコードを記述する必要がなくなります。

  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 Native Module用に新しいvalidateAddress関数を定義します。validateFunctionがパラメータとしてAddressオブジェクトを必要とすることに注意してください。

カスタム型を返す関数を持つことも可能です。

2. ブリッジングコードの定義

specsで定義されたAddress型から、Codegenは2つのヘルパー型、NativeSampleModuleAddressNativeSampleModuleAddressBridgingを生成します。

最初の型はAddressの定義です。2番目の型には、カスタム型をJSからC++へ、またその逆へブリッジングするためのすべてのインフラストラクチャが含まれています。追加で必要な唯一のステップは、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型エイリアスを定義します。ジェネリクスの順序は重要です。最初のテンプレート引数は構造体の最初のデータ型を参照し、2番目のテンプレート引数は2番目のデータ型を参照します。

次に、Codegenによって生成されたNativeSampleModuleAddressBridgingを拡張することで、新しいAddress型に対するBridging特殊化を追加します。

これらの型を生成するために従われる規則があります。

  • 名前の最初の部分は常にモジュールの型です。この例ではNativeSampleModule
  • 名前の2番目の部分は常に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::Stringstd::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++への最初の型をブリッジしました。