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

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

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

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

  1. JS 仕様の作成
  2. スカフォールディングを生成するための Codegen の設定
  3. ネイティブロジックの実装
  4. Android および iOS アプリケーションへのモジュールの登録
  5. JS での変更のテスト

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

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

1. JS 仕様の作成

純粋な C++ Turbo Native Module は Turbo Native Module です。Codegen がスカフォールディングコードを作成できるように、仕様ファイル (spec ファイルとも呼ばれます) が必要です。仕様ファイルは、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. Android アプリに OnLoad.cpp ファイルを作成して、新しい Turbo Native Module を登録します。

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 ファイルを見つけることができる場所を 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"
+ }
+ }
}

このブロックは、CMake ファイルを探す場所を Gradle ファイルに伝えます。パスは 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. Pods をインストールし、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 プロジェクトに追加する

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

  1. CocoaPods で生成された 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. JS モジュール名を ModuleProvider クラスに関連付けるように package.json を設定します。

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

3.1 ModuleProvider の作成
  1. Xcode から、SampleApp プロジェクトを選択し、 + N を押して新しいファイルを作成します。
  2. Cocoa Touch Class テンプレートを選択します。
  3. 名前 SampleNativeModuleProvider を追加します (他のフィールドは Subclass of: NSObject および Language: 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 の更新

最後のステップは、Native Module の 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 が再度実行されて新しいファイルが生成されるように、pods を再インストールする必要があります。

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

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

5. コードのテスト

C++ Turbo Native Module を JS からアクセスする時が来ました。そのためには、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