BridgeはReact Nativeのアーキテクチャコンポーネントであり、JavaScriptスレッドとiOS・Androidのネイティブ環境の間の非同期コミュニケーションを提供します。これはキューを経由してシリアライズされたJSONメッセージを送信し、JSコードからネイティブAPIを呼び出すことを可能にします。Meta, 2024によると、Bridgeは現存のアプリケーションの基盤として残っていますが、JSIベースの新アーキテクチャに比べてパフォーマンスで遅れをとっています。
メインポイント
BridgeはReact Nativeの重要なアーキテクチャ要素であり、アプリケーションのビジネスロジックが実行されるJavaScriptスレッドと、ネイティブのiOS・Androidスレッドの間で、双方向の非同期コミュニケーションを提供します。2015年のReact Nativeリリース以来、BridgeはJSコードがプラットフォームAPI—カメラ、地理位置情報、ファイルシステム、通知、その他のネイティブ機能—と相互作用する唯一の方法です。
Bridgeアーキテクチャはメッセージキューの原理に基づいています。JavaScriptコードがネイティブメソッドを呼ぶと、リクエストはJSON文字列にシリアライズされ、キューに入れられ、非同期的にネイティブ側に送信されます。ネイティブコードがリクエストを処理し、対応する操作を実行し、結果を同じキューを経由してJSスレッドに戻します。React Conf 2021でのMetaの報告によると、平均的なアプリでは秒間に最大10,000のメッセージがBridgeを通過しています。
Bridgeの動作に関わる主なスレッドは、JavaScript Thread(JSコードの実行、Native Thread(ネイティブ操作の実行、Shadow Thread(Yogaを使用したレイアウト計算です。それぞれのスレッドは独立して動作するため、UIのレスポンシブなす—ネイティブアニメーションがJSの計算でブロックされることはありません。
Bridgeはコミュニケーションに3つの主要なメカニズムを使用します: MessageQueue、JSONシリアライゼーション、メッセージのバッチ化。MessageQueueはReact Nativeの内部コンポーネントで、JSとネイティブ側の間の呼出しキューを管理します。各ネイティブメソッドの呼出しはキューに入れられ、シリアライズされ、パフォーマンス最適化のためにバッチで送信されます。
MessageQueueはバッチ化の原理で動作します: ネイティブメソッドの呼出しが蓄積され、5–15ミリ秒ごとに単一のグループ(バッチ)として送信されます。これにより、複数の呼出しが1つのJSONパッケージにぱっくされるため、シリアライゼーションのオーバーヘッドが減少します。ネイティブ側では、メッセージはデシリアライズされ、対応するモジュールに分配されます。
ネイティブモジュールは、マクロまたは注釈を通じて自動的に登録されます。iOSはRCT_EXPORT_MODULEマクロを使用し、Androidは@ReactMethod注釈を使用します。React Nativeはアプリケーション起動時に登録されたモジュールをスキャンし、利用可能なすべてのメソッドの構成JSONマップを構築します。このマップはJS環境に渡され、JavaScriptはどのメソッドが呼べるかを学びます。
データは次のパスを従います: JavaScriptがNativeModules.CalendarModule.createCalendarEvent()を呼び出します。メソッドは、モジュール識別子、メソッド名、引数を含むJSONメッセージにシリアライズされます。メッセージはMessageQueueに入ります。ネイティブスレッドで、メッセージはデシリアライズされ、対応するモジュールに渡されます。実行結果はシリアライズされて戻され、PromiseまたはcallbackとしてJSスレッドに送信されます。
// JavaScriptからBridgeを経由したネイティブモジュール呼び出し
import { NativeModules } from 'react-native';
const CalendarModule = NativeModules.CalendarModule;
CalendarModule.createCalendarEvent('Test Event', 'Office')
.then(eventId => {
console.log('次のIDでイベントが作成されました:', eventId);
})
.catch(error => {
console.error('失敗しました:', error);
});
ネイティブのiOS側では、モジュールはRCT_EXPORT_MODULEマクロを持つObjective-Cクラスとして表されます。メソッドはRCT_EXPORT_METHODマクロでエクスポートされ、React Nativeが自動的にBridgeに登録します。引数は位置で渡され、サポートされるJSON型(NSString、NSNumber、NSArray、NSDictionary、BOOL)に対応する必要があります。
// BridgeでのiOSネイティブモジュール登録
@interface CalendarModule () RCT_EXPORT_MODULE()
@end
@implementation CalendarModule
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSNumber *eventId = createEvent(name, location);
resolve(eventId);
}
@end
Bridgeにはいくつかの根本的なパフォーマンス制約があります。主なものは、強制的な非同期性とシリアライゼーションです。ネイティブメソッドの呼び出しごとにデータがJSON文字列に変換され、レンタンシの増加とメモリ消費を引き起こします。画像処理や動画処理など、大量のデータを扱う操作では、これがボトルネックとなります。
JSONのシリアライゼーションとデシリアライゼーションは、CPUタイムとメモリを消費します。各メッセージはJS側で文字列に変換され、ブリッジを経由して伝送され、ネイティブ側で解析される必要があります。Callstackのテスト(2022)によると、Bridgeを経由して10,000の数値からなる配列をシリアライズするのに約30–50ミリ秒かかり、高頻度の呼び出しには不可能です。
Bridgeは大きなバイナリデータの転送には最適化されていません。写真、音声ファイル、動画ストリームには代替アプローチが必要です—例えば、ファイルをディスクに書き出し、パスを文字列として渡す方法です。これにより、ファイルシステムの読み出しと書き込みに追加のオーバーヘッドが生じます。
これらの制約の認識により、Metaチームは新たなReact Nativeアーキテクチャを開発し、BridgeをJSI(JavaScript Interface)とTurbo Moduleに置き換えました。JSIは、シリアライゼーションなしで直接ネイティブメソッドを呼ぶことを可能にし、Bridgeの主な缺点を解消します。
BridgeとTurbo Moduleの比較は、アーキテクチャアプローチの根本的な違いを示しています。BridgeはJSONシリアライゼーションを伴う非同期メッセージキューを使用するの対し、Turbo ModuleはJSIを通して動作します—JavaScriptとC++の間の直接インタフェースで、データ変換なしにネイティブメソッドを同期的に呼ぶことができます。
| 特徴 | Bridge | Turbo Module |
|---|---|---|
| 呼び出しの種類 | 非同期 | 同期および非同期 |
| シリアライゼーション | 呼び出しごとにJSON | コピー不要のJSIオブジェクト |
| パフォーマンス | 中程度 | 高い |
| 型付け | 動的 | 静的 (Codegen) |
| ロード | 起動時にすべてのモジュール | 懒動的 (オンデマンド) |
BridgeとTurbo Moduleの選択は、React Nativeのバージョンに依存します。React Native 0.72以前のプロジェクトでは、Bridgeが主なメカニズムとなります。React Native 0.73よりも、Metroと新アーキテクチャが並行サポートされ、段階的な移行が可能になります。Turbo Moduleへの完全な移行には、React Native 0.76+へのアップグレードと、構成で新アーキテクチャの有効化が必要です。
カレンダーモジュールを例に、Bridgeを経由したNative Moduleの作成から使用までの完全なサイクルを見ていきます。このモジュールはイベントを作成し、その識別子を戻します。この例は、iOSとAndroidの両プラットフォームの設定をカバーしています。
Androidでは、Native ModuleはReactContextBaseJavaModuleを継承するJavaクラスとして作成されます。@ReactMethod注釈がメソッドをBridgeにエクスポートします。Promiseには、com.facebook.react.bridgeからのPromiseインタフェースが使用されます。
public class CalendarModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "CalendarModule";
}
@ReactMethod
public void createCalendarEvent(
String name,
String location,
Promise promise) {
try {
Integer eventId = createCalendarEventNative(name, location);
promise.resolve(eventId);
} catch (Exception e) {
promise.reject("EVENT_ERROR", e.getMessage());
}
}
}
モジュールは@ReactModuleを通じて、またはアプリケーションパッケージ内で手動で登録されます。React Nativeが自動的に検出してBridgeに追加します。登録後、モジュールはNativeModulesを経由してJavaScriptからアクセス可能になります。
public class CalendarPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new CalendarModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
新しいモジュールを追加する際、Bridgeはアプリの再起動が必要であることに注意してください。構成マップは初期化時に4回構築されるからです。これが、懒動的にロードされ、再起動なしでモジュールのホットリロードをサポートするTurbo Moduleとの違いです。
よくある質問
Bridgeは常に非同期キューとJSONシリアライゼーションを使用するの対し、JSIを経由する直接伝送は同期的に、データコピーなしで動作します。Bridgeはシリアライゼーションレンタンシはありますが、スレッドの精熶を確保します。
いいえ、Bridgeは非同期呼び出しのみをサポートしています。同期相互作用には、JSIとTurbo Moduleを使用した新しいアーキテクチャが必要です。これはReact Native 0.76+で解決された主な制約の一つです。
BridgeはJSONにシリアライズ可能な型をサポートしています: 文字列、数値、ブール値、配列、辞書(オブジェクト)。画像のようなバイナリデータは、ファイルシステムまたはbase64エンコードを経由して伝送する必要があります。
測定には、React DevToolsとReact Nativeプロファイラを使用します。Performanceタブでは、Bridgeキュー内のメッセージ数とレンタンシが表示されます。トラフィック監視にはreact-native-bridge-spyパッケージも利用可能です。
パフォーマンスが問題となるプロジェクトや、React Native 0.76+で新規アプリを作成する場合は、移行を推奨します。既存プロジェクトの場合、移行は段階的に行うことができます—両アーキテクチャは並行動作します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。