Native Moduleは、JavaまたはObjective-Cのクラスで、React NativeのJavaScriptからプラットフォームのネイティブAPIにアクセスできるようにします。各モジュールはBridgeに登録され、JSコードから通常の関数のように呼び出せるメソッドをエクスポートします。Meta、2024によると、Native ModuleはReact Nativeアプリケーションにプラットフォームコードを統合する主要な方法であり続けています。
重要なポイント
Native ModuleはReact Nativeのアーキテクチャ要素であり、プラットフォーム言語(iOSの場合はObjective-C/Swift、Androidの場合はJava/Kotlin)でコードを実行し、結果をJavaScriptに返すことを可能にします。Native Moduleなしでは、デバイスのネイティブ機能(カメラ、GPS、加速度計、ファイルシステム、Bluetooth)にアクセスすることはできません。
React Nativeには組み込みのNative Moduleが用意されています:CameraRoll、AsyncStorage、Geolocation、NetInfoなどです。ただし、特定のタスク(サードパーティSDKの統合、ハードウェアセンサーの操作、バックグラウンド処理)には、開発者がカスタムモジュールを作成します。State of React Native 2024の調査によると、67%の開発者がプロジェクトで少なくとも1つのカスタムNative Moduleを使用しています。
Native ModuleのアーキテクチャはReact Nativeのバージョンによって異なります。従来のアーキテクチャ(React Native 0.72以前)では、モジュールはBridgeを介して接続し、JSONシリアライゼーションを介してJSと非同期に通信します。新しいアーキテクチャ(React Native 0.76+)では、モジュールはシリアライゼーションなしで同期アクセスのためにJSIを使用するTurbo Moduleとして動作できます。
iOS用のNative Moduleの作成は、RCTBridgeModuleプロトコルを実装するObjective-Cクラスの宣言から始まります。RCT_EXPORT_MODULEマクロはモジュールをBridgeに登録し、RCT_EXPORT_METHODはJavaScriptから利用可能なメソッドをエクスポートします。
// ImageCompressor.m — iOS用Native Module
@interface ImageCompressor () RCT_EXPORT_MODULE()
@end
@implementation ImageCompressor
RCT_EXPORT_METHOD(compressImage:(NSString *)imagePath
quality:(NSNumber *)quality
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
NSData *compressedData = [UIImageJPEGRepresentation(image, quality.floatValue)];
NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"compressed.jpg"];
[compressedData writeToFile:outputPath atomically:YES];
resolve(outputPath);
}
@end
compressImageメソッドは画像パスと圧縮品質(0.0~1.0)を受け取り、ネイティブ側でデータを処理して圧縮ファイルのパスを返します。主な利点は、圧縮がネイティブコードによって実行されることで、同等のJavaScript操作よりも大幅に高速でメモリ効率が高いことです。
Swiftモジュールの場合、クラスとメソッドの前に@objcアノテーションを使用して、Bridgeが動作するObjective-Cランタイムからアクセスできるようにします。クラスはNSObjectを継承し、RCTBridgeModuleを実装する必要があります。
// ImageCompressor.swift — Swift Native Module
@objc(ImageCompressor)
class ImageCompressor: NSObject {
@objc
func compressImage(
_ imagePath: String,
quality: Float,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard let image = UIImage(contentsOfFile: imagePath) else {
rejecter("FILE_ERROR", "Cannot load image", nil)
return
}
guard let data = image.jpegData(compressionQuality: CGFloat(quality)) else {
rejecter("COMPRESS_ERROR", "Compression failed", nil)
return
}
let outputPath = NSTemporaryDirectory() + "compressed.jpg"
try? data.write(to: URL(fileURLWithPath: outputPath))
resolver(outputPath)
}
}
Androidでは、Native ModuleはReactContextBaseJavaModuleを拡張するJavaクラスとして作成されます。@ReactMethodアノテーションはメソッドをBridgeにエクスポートします。結果を返すには、com.facebook.react.bridgeのPromiseインターフェースが使用されます。
// ImageCompressorModule.java — Android用Native Module
public class ImageCompressorModule
extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "ImageCompressor";
}
@ReactMethod
public void compressImage(
String imagePath,
Float quality,
Promise promise) {
try {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
File outputFile = new File(
ReactNative.getApplicationContext()
.getCacheDir(), "compressed.jpg");
FileOutputStream fos = new FileOutputStream(outputFile);
bitmap.compress(
Bitmap.CompressFormat.JPEG,
(int)(quality * 100), fos);
fos.close();
promise.resolve(outputFile.getAbsolutePath());
} catch (Exception e) {
promise.reject("COMPRESS_ERROR", e.getMessage());
}
}
}
getName()メソッドは、JavaScriptからアクセス可能になるモジュール名を返します。上記の例では、モジュールはImageCompressorとして登録されています。@ReactMethodアノテーションは、メソッドをエクスポートする必要があることをBridgeに通知します。重要:メソッドはvoidでなければならず、Bridgeがサポートする型(String、Boolean、Integer、Double、ReadableArray、ReadableMap、Promise)のみを受け入れます。
モジュールクラスを作成した後、アプリケーションパッケージに登録する必要があります。これには、ReactPackageを実装するクラスを作成し、createNativeModulesメソッドでモジュールリストに追加します。
// ImageCompressorPackage.java — モジュール登録
public class ImageCompressorPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new ImageCompressorModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
両方のプラットフォーム用のモジュールを作成した後、それらをReact Nativeに登録する必要があります。Androidの場合、パッケージはMainApplication.javaのgetPackages()メソッドに追加されます。iOSの場合、モジュールはRCT_EXPORT_MODULEマクロを介して自動的に登録されますが、AppDelegate.mmでの手動登録も使用できます。
// MainApplication.java — React Nativeにパッケージを追加
import com.yourapp.nativemodules.ImageCompressorPackage;
public class MainApplication extends Application
implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages =
new PackageList(this).getPackages();
packages.add(new ImageCompressorPackage());
return packages;
}
};
}
登録後、モジュールはJavaScriptでNativeModulesを介して利用可能になります。React Nativeは、Androidの場合はgetName()、iOSの場合はRCT_EXPORT_MODULEで指定されたモジュール名を自動的に使用します。
// JavaScriptからNative Moduleを使用
import { NativeModules } from 'react-native';
import { Platform } from 'react-native';
const ImageCompressor = NativeModules.ImageCompressor;
async function compressPhoto(uri: string) {
try {
const result = await ImageCompressor.compressImage(
uri.replace('file://', ''), 0.8
);
console.log('圧縮済み:', result);
return result;
} catch (error) {
console.error('圧縮失敗:', error);
throw error;
}
}
従来のNative ModuleとTurbo Moduleの比較は、新しいプロジェクトにどのアプローチを選択するかを理解するのに役立ちます。両方のメカニズムがネイティブコードへのアクセスを提供しますが、アーキテクチャとパフォーマンスにおいて根本的に異なります。
| 特性 | Native Module(Bridge) | Turbo Module(JSI) |
|---|---|---|
| 通信 | JSONによる非同期 | JSIによる同期 |
| シリアライゼーション | 呼び出し毎にJSON | データコピーなし |
| 型付け | 手動、生成なし | Codegenによる自動 |
| 読み込み | アプリケーション初期化時 | 遅延読み込み(lazy load) |
| 互換性 | 全React Nativeバージョン | React Native 0.73+ |
React Native 0.72以前の既存プロジェクトでは、従来のNative Moduleが引き続き主要な選択肢です。新しいプロジェクトでは、特にネイティブメソッドの頻繁な呼び出しで高いパフォーマンスが必要な場合、Turbo Moduleが推奨されます。React Nativeの段階的なアップデートに伴い、コミュニティは新しいアーキテクチャへの完全な移行に向かっています。
よくある質問
はい、Bridgeはコールバックをサポートしています。Promiseの代わりに、iOSではRCTResponseSenderBlockコールバック関数、AndroidではCallbackを使用できます。ただし、Promiseは現代的な標準と見なされており、新しいモジュールには推奨されます。
Native Moduleは通常のネイティブコードと同様にデバッグされます — XcodeまたはAndroid Studioでブレークポイントを設定します。iOSの場合はReact Nativeを使用したビルドスキーム、Androidの場合はDebug構成を使用します。エントリポイントはJSから呼び出されたメソッドです。
Bridgeを介したNative Moduleは、バイナリデータ(NSData/byte[])、カスタムオブジェクト、関数をサポートしていません。画像の場合は、ファイルパスまたはbase64文字列を使用します。JSIを介したTurbo Moduleはこれらの制限の一部を解除します。
通常は不要です — Native Module APIは安定しており、下位互換性があります。新しいアーキテクチャ(Turbo Module)に移行する際、モジュールはデコレータを通じて適応されますが、既存のコードは引き続き動作します。
iOSではRCTEventEmitter、AndroidではDeviceEventEmitterを使用します。モジュールがイベントを送信し、JS側がreact-nativeのNativeEventEmitterを介して購読します。これはストリーミングデータやセンサーイベントに便利です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。