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

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

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

このガイドでは、純粋な C++ Turbo Native Module の作成について説明します。

  1. JS スペックを作成する
  2. スキャフォールディングを生成するように Codegen を設定する
  3. ネイティブロジックを実装する
  4. Android および iOS アプリケーションでモジュールを登録する
  5. 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 をサポートしています。

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

すべての Native Turbo Module スペックファイルには、プレフィックス Native が必要です。そうしないと、Codegen はそれらを無視します。

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. Codegen の設定

次のステップは、package.jsonCodegenを設定することです。ファイルに以下を含めるように更新します。

package.json
     "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++ コードが選択されるようにプラットフォームに適用する必要がある変更について検討します。

  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 ステートメントにより、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 を効果的にビルドできるようにするには、次のことを行う必要があります。

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

1. CMakeLists.txt ファイルを作成する

Android はビルドに CMake を使用します。CMake は、ビルドできるように、共有フォルダで定義したファイルにアクセスする必要があります。

  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. build.gradle を変更してカスタム C++ コードを含める

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

  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 フォルダから、次のコマンドを実行します。
sh
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

これらのステップは、React Native から元の OnLoad.cpp ファイルをダウンロードし、アプリで C++ Turbo Native Module をロードするために安全に上書きできるようにします。

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

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

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

iOS

iOS アプリが C++ Turbo Native Module を効果的にビルドできるようにするには、次のことを行う必要があります。

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

1. Pods をインストールし、Codegen を実行する。

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

bash
cd ios
bundle install
bundle exec pod install

2. 共有フォルダを iOS プロジェクトに追加する

このステップでは、shared フォルダをプロジェクトに追加して、Xcode から見えるようにします。

  1. CocoPods が生成した Xcode Workspace を開きます。
bash
cd ios
open SampleApp.xcworkspace
  1. 左側の SampleApp プロジェクトをクリックし、Add files to "Sample App"... を選択します。

Add Files to Sample App...

  1. shared フォルダを選択し、Add をクリックします。

Add Files to Sample App...

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

Xcode Project

3. アプリに Cxx Turbo Native Module を登録する

アプリに純粋な Cxx Turbo Native Module を登録するには、次のことを行う必要があります。

  1. ネイティブモジュール用の ModuleProvider を作成します。
  2. package.json を設定して、JS モジュール名を ModuleProvider クラスに関連付けます。

ModuleProvider は、純粋な C++ モジュールを iOS アプリの残りの部分と結合する Objective-C++ です。

3.1 ModuleProvider の作成
  1. Xcode から SampleApp プロジェクトを選択し、 + N を押して新しいファイルを作成します。
  2. Cocoa Touch Class テンプレートを選択します。
  3. 名前 SampleNativeModuleProvider を追加します (他のフィールドは Subclass of: NSObjectLanguage: Objective-C のままにします)。
  4. 次へをクリックしてファイルを生成します。
  5. SampleNativeModuleProvider.mSampleNativeModuleProvider.mm に名前変更します。mm 拡張子は Objective-C++ ファイルを示します。
  6. SampleNativeModuleProvider.h の内容を次のように実装します。
NativeSampleModuleProvider.h

#import <Foundation/Foundation.h>
#import <ReactCommon/RCTTurboModule.h>

NS_ASSUME_NONNULL_BEGIN

@interface NativeSampleModuleProvider : NSObject <RCTModuleProvider>

@end

NS_ASSUME_NONNULL_END

これは、RCTModuleProvider プロトコルに準拠する NativeSampleModuleProvider オブジェクトを宣言します。

  1. SampleNativeModuleProvider.mm の内容を次のように実装します。
NativeSampleModuleProvider.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 を次のように変更します。

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 を再インストールする必要があります。

bash
# from the ios folder
bundle exec pod install
open SampleApp.xcworkspace

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 をアプリにインポートします。
  • onPress コールバック内の const revString = SampleTurboModule.reverseString(value);: これは、アプリで Turbo Native Module を使用する方法です。
警告

この例をできるだけ短く保つために、スペックファイルをアプリに直接インポートしました。この場合のベストプラクティスは、スペックをラップする別のファイルを作成し、そのファイルをアプリケーションで使用することです。これにより、スペックの入力を準備し、JS でそれらをより細かく制御できます。

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

Android
iOS
Android Video
iOS video