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

クロスプラットフォームネイティブモジュール (C++)

C++でモジュールを作成することは、AndroidとiOS間でプラットフォームに依存しないコードを共有する最良の方法です。純粋なC++モジュールを使用すると、ロジックを一度だけ記述し、プラットフォーム固有のコードを記述する必要なく、すべてのプラットフォームですぐに再利用できます。

このガイドでは、純粋なC++ Turbo Native Moduleの作成手順を説明します。

  1. JS仕様の作成
  2. コード生成の設定と足場の生成
  3. ネイティブロジックの実装
  4. AndroidおよびiOSアプリケーションへのモジュールの登録
  5. JSでの変更のテスト

このガイドの残りの部分では、コマンドを実行してアプリケーションを作成済みであることを前提としています。

npx @react-native-community/cli@latest init SampleApp --version 0.76.0

1. JS仕様の作成

純粋なC++ Turbo Native ModuleはTurbo Native Moduleです。コード生成が足場コードを作成できるように、仕様ファイル(スペックファイルとも呼ばれます)が必要です。仕様ファイルは、JSでTurbo Native Moduleにアクセスするために使用するファイルでもあります。

スペックファイルは、型付きJS方言で記述する必要があります。React Nativeは現在、FlowまたはTypeScriptをサポートしています。

  1. アプリのルートフォルダ内に、`specs`という新しいフォルダを作成します。
  2. 次のコードを含む`NativeSampleModule.ts`という新しいファイルを作成します。
警告

すべてのネイティブTurbo Moduleのスペックファイルは、`Native`というプレフィックスを付ける必要があります。そうでない場合、コード生成はそれらを無視します。

specs/NativeSampleModule.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);

2. コード生成の設定

次のステップは、`package.json`でコード生成を設定することです。ファイルを更新して以下を含めます。

package.json
     "start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
}
},
"dependencies": {

この設定により、コード生成は`specs`フォルダ内のスペックファイルを探します。また、コード生成は`modules`のコードのみを生成し、生成されたコードの名前空間を`AppSpecs`にするよう指示します。

3. ネイティブコードの記述

C++ Turbo Native Moduleを記述すると、AndroidとiOS間でコードを共有できます。したがって、コードを一度記述し、C++コードを取り込めるようにプラットフォームに適用する必要がある変更について調べます。

  1. `android`フォルダと`ios`フォルダと同じレベルに`shared`という名前のフォルダを作成します。

  2. `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

  3. `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`ステートメントにより、コード生成によって作成され、実装に必要なインターフェースと基本クラスを含むスペックが含まれるようになります。
  • このモジュールは`facebook::react`名前空間に存在し、その名前空間に存在するすべての型にアクセスできます。
  • `NativeSampleModule`クラスは実際のTurbo Native Moduleクラスであり、いくつかのグルーコードとボイラープレートコードを含む`NativeSampleModuleCxxSpec`クラスを拡張して、このクラスをTurbo Native Moduleとして機能させます。
  • 最後に、必要に応じてJSと通信するための`CallInvoker`へのポインタを受け取るコンストラクタと、実装する必要がある関数のプロトタイプがあります。

`NativeSampleModule.cpp`ファイルは、Turbo Native Moduleの実際の実装であり、スペックで宣言したコンストラクタとメソッドを実装します。

4. プラットフォームへのモジュールの登録

次のステップでは、プラットフォームにモジュールを登録します。これは、ネイティブコードをJSに公開するステップであり、React Nativeアプリケーションが最終的にJSレイヤーからネイティブメソッドを呼び出せるようにします。

これは、プラットフォーム固有のコードを記述する唯一の時点です。

Android

AndroidアプリがC++ Turbo Native Moduleを効果的にビルドできるようにするには、次の手順が必要です。

  1. C++コードにアクセスするための`CMakeLists.txt`を作成します。
  2. 新しく作成された`CMakeLists.txt`ファイルを指すように`build.gradle`を変更します。
  3. 新しいTurbo Native Moduleを登録するために、Androidアプリに`OnLoad.cpp`ファイルを作成します。

1. `CMakeLists.txt`ファイルの作成

AndroidはCMakeを使用してビルドします。CMakeは、ビルドするために`shared`フォルダで定義したファイルにアクセスする必要があります。

  1. `SampleApp/android/app/src/main/jni`という新しいフォルダを作成します。`jni`フォルダは、AndroidのC++サイドが存在する場所です。
  2. `CMakeLists.txt`ファイルを作成し、このコンテキストを追加します。
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. カスタムC++コードを含めるように`build.gradle`を変更する

GradleはAndroidビルドをオーケストレートするツールです。Turbo Native Moduleをビルドするために、CMakeファイルを見つける場所をGradleに伝える必要があります。

  1. `SampleApp/android/app/build.gradle`ファイルを開きます。
  2. 既存の`android`ブロック内に、Gradleファイルに次のブロックを追加します。
android/app/build.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を要求すると、アプリはその場所を認識し、返すことができます。

  1. `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
  1. 次に、このファイルを次のように変更します。
android/app/src/main/jni/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

これらの手順により、C++ Turbo Native Moduleをアプリに安全にロードするために、React Nativeから元の`OnLoad.cpp`ファイルがダウンロードされます。

ファイルをダウンロードしたら、次のように変更できます。

  • モジュールを指すヘッダーファイルを含めます。
  • JSが要求したときにアプリが返すことができるように、Turbo Native Moduleを登録します。

これで、プロジェクトルートから`yarn android`を実行して、アプリが正常にビルドされることを確認できます。

iOS

iOSアプリがC++ Turbo Native Moduleを効果的にビルドできるようにするには、次の手順が必要です。

  1. Podをインストールして、コード生成を実行します。
  2. iOSプロジェクトに`shared`フォルダを追加します。
  3. アプリケーションにC++ Turbo Native Moduleを登録します。

1. Podのインストールとコード生成の実行

最初に実行する必要があるステップは、iOSアプリケーションの準備をするたびに実行する通常のステップです。CocoaPodsはReact Nativeの依存関係を設定してインストールするために使用するツールであり、このプロセスの一部として、コード生成も実行されます。

cd ios
bundle install
bundle exec pod install

2. iOSプロジェクトへの`shared`フォルダの追加

このステップでは、Xcodeで表示できるように`shared`フォルダをプロジェクトに追加します。

  1. CocoPodsによって生成されたXcodeワークスペースを開きます。
cd ios
open SampleApp.xcworkspace
  1. 左側の`SampleApp`プロジェクトをクリックし、「"Sample App"にファイルを追加...」を選択します。

Add Files to Sample App...

  1. `shared`フォルダを選択して、「追加」をクリックします。

Add Files to Sample App...

すべてが正しく実行された場合、左側のプロジェクトは次のようになります。

Xcode Project

3. アプリへのC++ Turbo Native Moduleの登録

最後のステップでは、iOSアプリに純粋なC++ Turbo Native Moduleの検索場所を指示します。

Xcodeで、`AppDelegate.mm`ファイルを開き、以下のように修正します。

SampleApp/AppDelegate.mm
#import <React/RCTBundleURLProvider.h>
+ #import <RCTAppDelegate+Protected.h>
+ #import "NativeSampleModule.h"

// ...
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

+- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
+ jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
+{
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
+
+ return [super getTurboModule:name jsInvoker:jsInvoker];
+}

@end

これらの変更は、いくつかのことを行います。

  1. `RCTAppDelegate+Protected`ヘッダーをインポートして、`AppDelegate`が`RCTTurboModuleManagerDelegate`プロトコルに準拠していることを可視化します。
  2. 純粋なC++ネイティブTurbo Moduleインターフェース`NativeSampleModule.h`をインポートします。
  3. C++モジュール用の`getTurboModule`メソッドをオーバーライドします。これにより、JS側が`NativeSampleModule`というモジュールを要求した際に、アプリはどのモジュールを返す必要があるかを知ることができます。

これでXcodeからアプリケーションをビルドすれば、正常にビルドできるはずです。

5. コードのテスト

今度は、JSからC++ Turbo Native Moduleにアクセスする時間です。そのためには、`App.tsx`ファイルを修正してTurbo Native Moduleをインポートし、コード内で呼び出す必要があります。

  1. `App.tsx`ファイルを開きます。
  2. テンプレートの内容を以下のコードに置き換えます。
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をインポートします。
  • `const revString = SampleTurboModule.reverseString(value);` (`onPress`コールバック内): これは、アプリでTurbo Native Moduleを使用する方法です。
警告

この例を簡潔にするため、アプリにスペックファイルを直接インポートしました。ベストプラクティスとしては、スペックをラップする別のファイルを作成し、そのファイルを利用するのが良いでしょう。これにより、スペックへの入力を準備し、JSでより詳細な制御が可能になります。

おめでとうございます!最初のC++ Turbo Native Moduleを作成しました!

Android
iOS
Android Video
iOS video