クロスプラットフォームネイティブモジュール (C++)
C++ でモジュールを作成することは、Android と iOS の間でプラットフォームに依存しないコードを共有するための最良の方法です。純粋な C++ モジュールを使用すると、ロジックを一度書くだけで、プラットフォーム固有のコードを記述する必要なく、すべてのプラットフォームからすぐに再利用できます。
このガイドでは、純粋な C++ Turbo Native Module の作成について説明します。
- JS スペックを作成する
- スキャフォールディングを生成するように Codegen を設定する
- ネイティブロジックを実装する
- Android および iOS アプリケーションでモジュールを登録する
- JS で変更をテストする
このガイドの残りの部分では、コマンドを実行してアプリケーションを作成していることを前提としています。
npx @react-native-community/cli@latest init SampleApp --version 0.80.0
1. JS スペックを作成する
純粋な C++ Turbo Native Module は Turbo Native Module です。Codegen がスキャフォールディングコードを生成できるように、仕様ファイル (スペックファイルとも呼ばれます) が必要です。仕様ファイルは、JS で Turbo Native Module にアクセスするためにも使用します。
スペックファイルは、型付き JS 方言で記述する必要があります。React Native は現在、Flow または TypeScript をサポートしています。
- アプリのルートフォルダ内に、
specs
という新しいフォルダを作成します。 - 次のコードで
NativeSampleModule.ts
という新しいファイルを作成します。
すべての Native Turbo Module スペックファイルには、プレフィックス Native
が必要です。そうしないと、Codegen はそれらを無視します。
- TypeScript
- フロー
// @flow
import type {TurboModule} from 'react-native'
import { TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
"NativeSampleModule"
): Spec);
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
2. Codegen の設定
次のステップは、package.json
でCodegenを設定することです。ファイルに以下を含めるように更新します。
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
}
},
"dependencies": {
この設定により、Codegen は specs
フォルダ内のスペックファイルを検索するように指示されます。また、Codegen は modules
に対してのみコードを生成し、生成されたコードを AppSpecs
として名前空間化するように指示されます。
3. ネイティブコードの記述
C++ Turbo Native Module を記述すると、Android と iOS の間でコードを共有できます。したがって、コードを一度記述し、C++ コードが選択されるようにプラットフォームに適用する必要がある変更について検討します。
-
android
およびios
フォルダと同じレベルにshared
というフォルダを作成します。 -
shared
フォルダ内に、NativeSampleModule.h
という新しいファイルを作成します。shared/NativeSampleModule.h#pragma once
#include <AppSpecsJSI.h>
#include <memory>
#include <string>
namespace facebook::react {
class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);
std::string reverseString(jsi::Runtime& rt, std::string input);
};
} // namespace facebook::react -
shared
フォルダ内に、NativeSampleModule.cpp
という新しいファイルを作成します。shared/NativeSampleModule.cpp#include "NativeSampleModule.h"
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());
}
} // namespace facebook::react
作成した2つのファイルを見てみましょう
NativeSampleModule.h
ファイルは、純粋な C++ TurboModule のヘッダーファイルです。include
ステートメントにより、Codegen によって作成され、実装する必要があるインターフェイスと基底クラスを含むスペックが含まれていることが保証されます。- モジュールは、その名前空間に存在するすべての型にアクセスできるように、
facebook::react
名前空間に存在します。 - クラス
NativeSampleModule
は、実際の Turbo Native Module クラスであり、このクラスを Turbo Native Module として機能させるためのグルーコードとボイラープレートコードを含むNativeSampleModuleCxxSpec
クラスを拡張します。 - 最後に、必要に応じて JS と通信するための
CallInvoker
へのポインタを受け入れるコンストラクタと、実装する必要がある関数のプロトタイプがあります。
NativeSampleModule.cpp
ファイルは、Turbo Native Module の実際の実装であり、スペックで宣言したコンストラクタとメソッドを実装します。
4. モジュールをプラットフォームに登録する
次のステップでは、モジュールをプラットフォームに登録します。これにより、ネイティブコードが JS に公開され、React Native アプリケーションが JS レイヤーからネイティブメソッドを最終的に呼び出すことができます。
これは、プラットフォーム固有のコードを記述する必要がある唯一の時です。
Android
Android アプリが C++ Turbo Native Module を効果的にビルドできるようにするには、次のことを行う必要があります。
- C++ コードにアクセスするための
CMakeLists.txt
を作成します。 build.gradle
を変更して、新しく作成したCMakeLists.txt
ファイルを指すようにします。- 新しい Turbo Native Module を登録するために、Android アプリに
OnLoad.cpp
ファイルを作成します。
1. CMakeLists.txt
ファイルを作成する
Android はビルドに CMake を使用します。CMake は、ビルドできるように、共有フォルダで定義したファイルにアクセスする必要があります。
- 新しいフォルダ
SampleApp/android/app/src/main/jni
を作成します。jni
フォルダは、Android の C++ 側が存在する場所です。 CMakeLists.txt
ファイルを作成し、このコンテキストを追加します。
cmake_minimum_required(VERSION 3.13)
# Define the library name here.
project(appmodules)
# This file includes all the necessary to let you build your React Native application
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
# Define where the additional source code lives. We need to crawl back the jni, main, src, app, android folders
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ../../../../../shared/NativeSampleModule.cpp)
# Define where CMake can find the additional header files. We need to crawl back the jni, main, src, app, android folders
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)
CMake ファイルは次のことを行います。
- すべてのアプリ C++ コードが含まれる
appmodules
ライブラリを定義します。 - ベースの React Native の CMake ファイルをロードします。
target_sources
ディレクティブを使用してビルドする必要があるモジュール C++ ソースコードを追加します。デフォルトでは、React Native はすでにappmodules
ライブラリにデフォルトのソースを入力していますが、ここではカスタムのソースを含めます。C++ Turbo Module が存在するshared
フォルダにjni
フォルダから戻る必要があることがわかります。- CMake がモジュールヘッダーファイルを見つけることができる場所を指定します。この場合も、
jni
フォルダから戻る必要があります。
2. build.gradle
を変更してカスタム C++ コードを含める
Gradle は Android ビルドをオーケストレーションするツールです。Turbo Native Module をビルドするために CMake
ファイルを見つける場所を伝える必要があります。
SampleApp/android/app/build.gradle
ファイルを開きます。- 既存の
android
ブロック内に、次のブロックを Gradle ファイルに追加します。
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dokyumento.jp/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
+ externalNativeBuild {
+ cmake {
+ path "src/main/jni/CMakeLists.txt"
+ }
+ }
}
このブロックは、Gradle ファイルに CMake ファイルを探す場所を伝えます。パスは build.gradle
ファイルが存在するフォルダからの相対パスなので、jni
フォルダ内の CMakeLists.txt
ファイルへのパスを追加する必要があります。
3. 新しい Turbo Native Module を登録する
最後のステップは、新しい C++ Turbo Native Module をランタイムに登録することです。これにより、JS が C++ Turbo Native Module を要求したときに、アプリがそれを見つける場所を知り、それを返すことができます。
SampleApp/android/app/src/main/jni
フォルダから、次のコマンドを実行します。
curl -O https://raw.githubusercontent.com/facebook/react-native/v0.76.0/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp
- 次に、このファイルを次のように変更します。
#include <DefaultComponentsRegistry.h>
#include <DefaultTurboModuleManagerDelegate.h>
#include <autolinking.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncore.h>
+ // Include the NativeSampleModule header
+ #include <NativeSampleModule.h>
//...
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
// Here you can provide your CXX Turbo Modules coming from
// either your application or from external libraries. The approach to follow
// is similar to the following (for a module called `NativeCxxModuleExample`):
//
// if (name == NativeCxxModuleExample::kModuleName) {
// return std::make_shared<NativeCxxModuleExample>(jsInvoker);
// }
+ // This code register the module so that when the JS side asks for it, the app can return it
+ if (name == NativeSampleModule::kModuleName) {
+ return std::make_shared<NativeSampleModule>(jsInvoker);
+ }
// And we fallback to the CXX module providers autolinked
return autolinking_cxxModuleProvider(name, jsInvoker);
}
// leave the rest of the file
これらのステップは、React Native から元の OnLoad.cpp
ファイルをダウンロードし、アプリで C++ Turbo Native Module をロードするために安全に上書きできるようにします。
ファイルをダウンロードしたら、次のように変更できます。
- モジュールを指すヘッダーファイルを含める
- JS がそれを要求したときに、アプリがそれを返すことができるように、Turbo Native Module を登録する。
これで、プロジェクトルートから yarn android
を実行して、アプリが正常にビルドされるのを確認できます。
iOS
iOS アプリが C++ Turbo Native Module を効果的にビルドできるようにするには、次のことを行う必要があります。
- Pod をインストールし、Codegen を実行します。
shared
フォルダを iOS プロジェクトに追加します。- アプリケーションで C++ Turbo Native Module を登録します。
1. Pods をインストールし、Codegen を実行する。
最初に実行する必要があるステップは、iOS アプリケーションを準備する必要があるたびに実行する通常のステップです。CocoaPods は、React Native の依存関係を設定およびインストールするために使用するツールであり、そのプロセスの一部として、Codegen も実行します。
cd ios
bundle install
bundle exec pod install
2. 共有フォルダを iOS プロジェクトに追加する
このステップでは、shared
フォルダをプロジェクトに追加して、Xcode から見えるようにします。
- CocoPods が生成した Xcode Workspace を開きます。
cd ios
open SampleApp.xcworkspace
- 左側の
SampleApp
プロジェクトをクリックし、Add files to "Sample App"...
を選択します。
shared
フォルダを選択し、Add
をクリックします。
すべて正しく行った場合、左側のプロジェクトは次のようになります。
3. アプリに Cxx Turbo Native Module を登録する
アプリに純粋な Cxx Turbo Native Module を登録するには、次のことを行う必要があります。
- ネイティブモジュール用の
ModuleProvider
を作成します。 package.json
を設定して、JS モジュール名を ModuleProvider クラスに関連付けます。
ModuleProvider は、純粋な C++ モジュールを iOS アプリの残りの部分と結合する Objective-C++ です。
3.1 ModuleProvider の作成
- Xcode から
SampleApp
プロジェクトを選択し、⌘ + N を押して新しいファイルを作成します。 Cocoa Touch Class
テンプレートを選択します。- 名前
SampleNativeModuleProvider
を追加します (他のフィールドはSubclass of: NSObject
とLanguage: Objective-C
のままにします)。 - 次へをクリックしてファイルを生成します。
SampleNativeModuleProvider.m
をSampleNativeModuleProvider.mm
に名前変更します。mm
拡張子は Objective-C++ ファイルを示します。SampleNativeModuleProvider.h
の内容を次のように実装します。
#import <Foundation/Foundation.h>
#import <ReactCommon/RCTTurboModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface NativeSampleModuleProvider : NSObject <RCTModuleProvider>
@end
NS_ASSUME_NONNULL_END
これは、RCTModuleProvider
プロトコルに準拠する NativeSampleModuleProvider
オブジェクトを宣言します。
SampleNativeModuleProvider.mm
の内容を次のように実装します。
#import "NativeSampleModuleProvider.h"
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import "NativeSampleModule.h"
@implementation NativeSampleModuleProvider
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeSampleModule>(params.jsInvoker);
}
@end
このコードは、getTurboModule:
メソッドが呼び出されたときに純粋な C++ NativeSampleModule
を作成することで RCTModuleProvider
プロトコルを実装します。
3.2 package.json の更新
最後のステップは、ネイティブモジュールの JS スペックとネイティブコードでのこれらのスペックの具体的な実装との間のリンクについて React Native に伝えるために package.json
を更新することです。
package.json
を次のように変更します。
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
},
"ios": {
"modulesProvider": {
"NativeSampleModule": "NativeSampleModuleProvider"
}
}
},
"dependencies": {
この時点で、codegen が再度実行されて新しいファイルが生成されるように、pod を再インストールする必要があります。
# from the ios folder
bundle exec pod install
open SampleApp.xcworkspace
Xcode からアプリケーションをビルドすると、正常にビルドできるはずです。
5. コードのテスト
これで、JS から C++ Turbo Native Module にアクセスする時が来ました。そのためには、App.tsx
ファイルを変更して Turbo Native Module をインポートし、コード内でそれを呼び出す必要があります。
App.tsx
ファイルを開きます。- テンプレートの内容を次のコードに置き換えます。
import React from 'react';
import {
Button,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import SampleTurboModule from './specs/NativeSampleModule';
function App(): React.JSX.Element {
const [value, setValue] = React.useState('');
const [reversedValue, setReversedValue] = React.useState('');
const onPress = () => {
const revString = SampleTurboModule.reverseString(value);
setReversedValue(revString);
};
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C++ Turbo Native Module Example
</Text>
<Text>Write down here he 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>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
marginBottom: 20,
},
textInput: {
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 10,
marginTop: 10,
},
});
export default App;
このアプリで興味深い行は次のとおりです。
import SampleTurboModule from './specs/NativeSampleModule';
: この行は、Turbo Native Module をアプリにインポートします。onPress
コールバック内のconst revString = SampleTurboModule.reverseString(value);
: これは、アプリで Turbo Native Module を使用する方法です。
この例をできるだけ短く保つために、スペックファイルをアプリに直接インポートしました。この場合のベストプラクティスは、スペックをラップする別のファイルを作成し、そのファイルをアプリケーションで使用することです。これにより、スペックの入力を準備し、JS でそれらをより細かく制御できます。
おめでとうございます。最初の C++ Turbo Native Module を作成しました!
![]() | ![]() |