JSI(JavaScript Interface)は、React Nativeのソフトウェアレイヤーで、JavaScriptからC++のオブジェクトや関数への直接的な同期アクセスを提供し、非同期JSONブリッジであるBridgeを置き換えます。前任者とは異なり、JSIはメッセージのシリアライズなしでネイティブメソッドを呼び出し、C++オブジェクトへの参照を直接JS環境に渡すことができます。React Native Team(2025)によると、JSIはデータ集約型のシナリオにおいて、JSとネイティブコードの相互作用を最大10倍高速化します。
重要なポイント
JSI(JavaScript Interface)は、JavaScriptエンジン(Hermes、JavaScriptCore、V8)にC++オブジェクト、関数、メモリへの直接アクセスを提供するC++レイヤーです。呼び出しをJSONにシリアライズして非同期キューを通して渡していたBridgeとは異なり、JSIはJSコードが同期的にC++メソッドを呼び出し、即座に結果を取得することを可能にします。
JSIはReact Native 0.64で新しいアーキテクチャ(New Architecture)の一部として導入されました。主な目的は、Bridgeがもたらすボトルネックを排除することでした。JSとネイティブコードの間の各相互作用は、シリアライズ、デシリアライズ、メッセージキューの通過に時間を費やしていました。JSIは、jsi::Value、jsi::Object、jsi::Functionインターフェースを実装するラッパーを通じて、JSエンジンにC++オブジェクトへの直接アクセスを提供することでこの問題を解決します。
JSIはBridgeの1対1の置き換えではありません — 統合に対する根本的に異なるアプローチです。Bridgeはメールボックスのように機能していました。JSがメッセージを送信し、キューを通過し、ネイティブ側が処理して返信を送信していました。JSIはポインタのように機能します。JSはC++オブジェクトへの参照を取得し、通常のJS関数と同様にそのメソッドを同期的に呼び出すことができます。これは2つの環境間の相互作用アーキテクチャにおける根本的な違いです。
JSIの必要性は、React Native 2015で確立されたオリジナルのBridgeの制限から生じました。フレームワークの人気が高まり、アプリケーションが複雑になるにつれて、パフォーマンスの問題が明らかになりました。各ネイティブモジュール呼び出しは、シリアライズに少なくとも3~5ミリ秒を必要としていました。センサー値の読み取りや画面サイズの取得などの単純な操作では許容範囲でしたが、アニメーション、グラフィックス処理、ストリーミングデータ処理では重大な問題でした。React Nativeチームは2019年に新しいアーキテクチャの作業を開始し、JSIがその基盤となりました。
JSIはJavaScriptエンジン上の抽象化レイヤーとして設計されています。Hermes、JavaScriptCore(iOS)、V8(Android)の各エンジンに対して実装された統一C++ APIを提供します。つまり、開発者はエンジン間の違いを気にする必要がありません — FabricとTurboModulesは、内部で使用されているJSエンジンに関係なく同じように動作します。
JSIの中核にはホストオブジェクト(Host Objects)の概念があります — C++オブジェクトがネイティブJSオブジェクトとしてJS環境にエクスポートされます。JSコードがそのようなオブジェクトのプロパティやメソッドにアクセスすると、JSIが呼び出しをインターセプトし、対応するC++メソッドに委任します。これは同期的に、同じスレッドで、コンテキストスイッチなしで、JSON文字列のメモリ割り当てなしで行われます。
各ホストオブジェクトは、get、set、getPropertyNamesメソッドを持つjsi::HostObjectインターフェースを実装します。JSエンジンは、オブジェクトのプロパティにアクセスされるたびにこれらのメソッドを呼び出します。例えば、JSでNativeModule.someMethod()を呼び出すと、JSIはこの呼び出しを対応するホストオブジェクトメソッドへのC++呼び出しに変換します。戻り値はjsi::ValueとしてJSに返されます — 数値、文字列、ブール値、オブジェクト、またはundefinedを表すことができる汎用型です。
JSIの重要な特徴はメッセージキューがないことです。Bridgeは非同期キューを使用していました。JSがリクエストを送信し、他のタスクに切り替え、ネイティブ側がリクエストを処理し、結果がコールバックを介して返されていました。JSIは同期的に動作します。JSがJSIを介してネイティブモジュールメソッドを呼び出すと、結果が受信されるまでJSコードの実行が一時停止します。これによりロジックが簡素化され(コールバックを待つ必要がない)、競合状態が排除されますが、注意が必要です — 長時間の同期呼び出しはJSスレッドをブロックします。
JSIを介して作成された値はJSエンジンのランタイムに存在し、ガベージコレクタによって管理されます。C++コードがjsi::Stringやjsi::Objectを作成してJSに返すと、環境が自動的にメモリを管理します。C++コードが呼び出し間でJS値への参照を保持したい場合は、jsi::Value::getWeak()またはランタイムルートオブジェクトに保存された参照を持つグローバルなjsi::Object::setPropertyが使用されます。これにより、ガベージコレクタによる早期削除が防止されます。
JSIはデフォルトではスレッドセーフではありません。すべてのJSIメソッド呼び出しは、JSが実行されるスレッド(通常はReact NativeのJSスレッド)から行われる必要があります。ネイティブモジュールが別のスレッドでバックグラウンド処理を開始する場合、結果はTurboModulesのrunOnJSを使用してJSスレッドを介して返される必要があります。この制限は、同期性とシリアライズがないことの代償です。
JSIとBridgeの違いは根本的であり、JSとネイティブコードの相互作用のすべての側面に影響します。Bridgeは非同期で、データをJSONにシリアライズし、メッセージキューを使用していました。JSIは同期で、ネイティブ参照で動作し、シリアライズを必要としません。
| パラメータ | Bridge | JSI |
|---|---|---|
| 呼び出しモデル | 非同期キュー | 同期直接呼び出し |
| シリアライズ | JSON(シリアライズ+デシリアライズ) | なし(C++オブジェクトへの直接参照) |
| レイテンシ | 呼び出しあたり3~10ミリ秒 | 呼び出しあたり0.1~0.5ミリ秒 |
| 型付け | 動的(JSON経由) | 静的(Codegen経由) |
| C++統合 | ネイティブモジュール経由のみ(Java/ObjC) | 直接、仲介者なし |
| スレッド | 別のネイティブスレッド | JSスレッド(同期) |
React Native Teamによると、Facebook MarketplaceアプリでBridgeからJSIに移行したことで、JSとネイティブ間のデータ重複を排除し、起動時間が35%短縮され、メモリ消費が20%削減されました。
Bridgeは“間違い”ではありませんでした — 2015年のReact Native作成時に正当化されたアーキテクチャ上の決定でした。異なる言語を持つ2つのプラットフォーム向けのネイティブ開発には、普遍的な交換フォーマットが必要でした。シリアライズフォーマットとしてのJSONはすべてのプラットフォームで利用可能で、相互作用を統一することができました。問題が明らかになったのは、React Nativeが1秒あたり数千のネイティブモジュール呼び出しを行う複雑なアプリケーションに使われ始めた後でした。
React Nativeは後方互換性を維持しています。Bridge用に書かれたネイティブモジュールは、互換性レイヤーを通じて新しいアーキテクチャでも引き続き動作します。ただし、新しいモジュールについては、TurboModulesを介してJSIを直接使用することを推奨します。既存のモジュールの移行は、モジュールのビジネスロジックを変更せずに相互作用プロトコルを置き換えることを意味します。
JSIは、新しいReact Nativeアーキテクチャのすべてのコンポーネントが構築される基盤となるレイヤーです。JSIがなければ、Fabric(新しいレンダラー)もTurboModules(最適化されたネイティブモジュール)も不可能です。JSIは、すべてのレベルでJSがC++と相互作用する統一された方法を提供します。
Fabricは、C++ UI表現への同期アクセスにJSIを使用する新しいReact Nativeレンダラーです。古いアーキテクチャでは、レンダリングはBridgeを通じて行われていました。JSがReact要素を作成し、JSONにシリアライズし、Bridgeを通じて送信し、ネイティブ側がデシリアライズしてUIを作成していました。FabricはJSIを通じてJSから直接C++シャドウツリーオブジェクトを作成し、Yogaを介して同期的にレイアウトを計算し、準備完了したフレームをネイティブレンダラーに渡します — シリアライズは一切行われません。
TurboModulesはReact Nativeネイティブモジュールの進化形です。Bridgeにモジュールを登録してJSONを介してメソッドを呼び出す代わりに、TurboModulesは遅延読み込みと直接呼び出しにJSIを使用します。JSコードが最初にモジュールにアクセスすると、JSIがホストオブジェクトを作成します — ネイティブモジュールをロードし、そのメソッドをC++関数として公開します。遅延読み込みとは、モジュールが最初のアクセスまでメモリを消費しないことを意味します — これは、数十のネイティブモジュールを持つアプリケーションで特に重要であり、その多くは特定の画面でのみ使用されます。
新しいアーキテクチャでJSIを扱うために、Codegen — JavaScript仕様からC++バインディングを生成するツール — が使用されます。開発者はTypeScriptまたはFlowでネイティブモジュールインターフェースを記述し、CodegenがJSI互換のホストオブジェクトを実装するC++コードを生成します。これによりルーチンワークが自動化され、JS側とC++側の型が同期されることが保証されます。
実際にJSIを扱う様子を見てみましょう。この例では、JSIを介してJSにエクスポートされる単純なC++クラスを作成し、React NativeのJavaScriptコードからそのメソッドを呼び出します。
// Calculator.h — JSからアクセス可能なC++クラスヘッダ
class Calculator {
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
Calculatorクラスには2つの算術メソッドがあります。これをJSからアクセス可能にする必要があります。そのために、Calculatorをラップし、JSIを介してそのメソッドを公開するホストオブジェクトが作成されます。
// CalculatorHostObject.cpp — JSIラッパーの実装
class CalculatorHostObject : public jsi::HostObject {
private:
Calculator calc;
public:
jsi::Value get(jsi::Runtime& runtime,
const jsi::PropNameID& name) override {
auto propName = name.utf8(runtime);
if (propName == "add") {
return jsi::Function::createFromHostFunction(
runtime, name, 2,
[this](jsi::Runtime& runtime,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) -> jsi::Value {
return jsi::Value(calc.add(
args[0].asNumber(),
args[1].asNumber()));
});
}
return jsi::Value::undefined();
}
};
このコードでは、JSがオブジェクトのプロパティにアクセスするたびにgetメソッドが呼び出されます。プロパティ名が“add”の場合、JSから2つの引数を受け取りcalc.add()を呼び出すC++関数が返されます。値はjsi::Valueとして返されます — JSIが自動的にdoubleをJS数値に変換します。
JS環境にホストオブジェクトを登録した後、呼び出しは通常のJS関数のように見えます。すべての型はコード生成段階でチェックされ、実行時の型不一致エラーを排除します。
// JavaScript — JSIを介したC++電卓の呼び出し
import { Calculator } from 'react-native-calculator'
const result = Calculator.add(5, 3)
console.log(result) // 8 — 同期、遅延なし
const product = Calculator.multiply(4, 2.5)
console.log(product) // 10 — 即時結果
注意:結果は即座に返され、Promise、await、コールバックはありません。これはBridgeアーキテクチャでは不可能だった同期呼び出しです。長時間実行される操作(ファイル読み取り、ネットワークリクエスト)には、非同期パターンを使用する必要があります — JSIは重いタスクに対するバックグラウンドスレッドの必要性を排除しません。
実際には、ほとんどの開発者はJSIホストオブジェクトを手動で作成しません — この作業は、TypeScript仕様に基づいてC++ラッパーを生成するCodegenが行います。ただし、JSIが内部でどのように機能するかを理解することは、効果的なパフォーマンスデバッグや、C++ライブラリ(Skia、FFmpeg、OpenCV)への直接アクセスを必要とする複雑なネイティブモジュールを作成する際に必要です。
よくある質問
BridgeはJSONシリアライズとメッセージキューを通じて非同期的に動作します — 各呼び出しはデータ変換に3~10ミリ秒かかります。JSIはシリアライズなしでC++オブジェクトへの同期直接アクセスを提供し、レイテンシを0.1~0.5ミリ秒に削減します。JSIはコピーではなくオブジェクト参照の受け渡しもサポートしています。
はい、JSIはHermes(React Nativeデフォルト)、JavaScriptCore(iOS)、V8(Android)向けに実装された統一C++ APIを提供します。開発者は異なるエンジン向けに異なるコードを書く必要はありません — FabricとTurboModulesはサポートされているすべてのエンジンで同じように動作します。
はい、React Nativeは後方互換性レイヤーを提供します。Bridge用に書かれたネイティブモジュールは新しいアーキテクチャでも引き続き動作します。ただし、JSIの利点 — 遅延読み込みと同期呼び出し — を得るために、TurboModulesに移行することを推奨します。
日常的な開発には — いいえ。ネイティブモジュールのTypeScript仕様は、Codegenを通じて自動的にC++バインディングにコンパイルされます。C++の知識は、カスタムC++ライブラリを作成する場合や、ランタイムレベルでJSIのパフォーマンスをデバッグする場合にのみ必要です。
JSIはBridgeの3つの主要な問題を解決します。JSONシリアライズによる高レイテンシ、同期呼び出しの欠如、複雑なオブジェクトを参照で渡せないことです。JSIはJavaやObjective-Cの仲介者なしでC++ライブラリを直接統合することも可能にします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。