はじめに
React Nativeでネイティブ機能を呼び出したいとき、「ブリッジの書き方が複雑すぎる」「JSとネイティブの型がズレてクラッシュした」という経験はありませんか。カメラ、センサー、暗号化処理など、JavaScriptだけでは完結しない処理は意外と多く、そのたびにネイティブモジュールの作法と格闘することになります。
そんな悩みに真正面から応えるのが、今回紹介するNitro Modulesです。TypeScriptでインターフェースを書くだけで、型安全なSwift/Kotlin/C++のバインディングが自動生成される。しかも既存の仕組みより圧倒的に速い。React Native界隈で注目を集めるこのフレームワークを、コード例とともに見ていきましょう。
Nitro Modulesとは
Nitro Modulesは、react-native-vision-cameraの作者として知られるMarc Rousavy氏(Margelo社)が開発している、React Native向けのネイティブモジュール構築フレームワークです。「Insanely fast(異常なほど速い)」を掲げ、静的にコンパイルされたJSIバインディング層によって、JavaScriptとネイティブコード間の通信を極限まで高速化しています。
プロジェクトは2つのパッケージで構成されています。
- react-native-nitro-modules - コアとなるC++ライブラリ
- nitrogen - TypeScriptの型定義からネイティブコードを生成するコードジェネレーター
主な特徴
- 圧倒的なパフォーマンス - 高度に最適化されたJSI(JavaScript Interface)基盤の上に構築され、プロパティ参照のキャッシュなどにより、従来のTurbo Modulesよりも高速な呼び出しを実現します
- TypeScriptファーストの型安全性 - コードジェネレーター「Nitrogen」がTypeScriptのインターフェースからC++/Swift/Kotlinの型を静的に生成するため、JSとネイティブの型のズレがコンパイル時に検出されます
- Swift/Kotlinを直接ブリッジ - iOSではSwift 5.9のC++相互運用を活用してObjective-Cのメッセージ送信を回避し、AndroidではfbjniによりKotlin/Javaと効率的に連携します
- 柔軟な型サポート - 数値や文字列はもちろん、オブジェクト、配列、Promise、コールバック関数まで、ほぼすべてのJavaScript型をそのまま扱えます
インストール
コアライブラリをプロジェクトに追加します。
# npm
npm install react-native-nitro-modules
# yarn
yarn add react-native-nitro-modules
# pnpm
pnpm add react-native-nitro-modules
iOSの場合はPodのインストールも実行します。
cd ios && pod install
自分でネイティブモジュールを作る場合は、コードジェネレーターのNitrogenを開発依存として追加します。
npm install nitrogen --save-dev
基本的な使い方
Nitro Modulesの中心概念はHybridObjectです。TypeScriptで「ネイティブ側に実装してほしいインターフェース」を宣言すると、Nitrogenが各プラットフォーム向けの仕様(Spec)を生成してくれます。
1. TypeScriptでインターフェースを定義する
Nitrogenは.nitro.tsで終わるファイルを解析対象とします。簡単な計算モジュールを定義してみましょう。
// Math.nitro.ts
import type { HybridObject } from 'react-native-nitro-modules'
interface Math extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
readonly pi: number
add(a: number, b: number): number
}
ジェネリクスの部分で「iOSはSwift、AndroidはKotlinで実装する」と宣言しているのがポイントです。
2. コードを生成する
プロジェクトルートにnitro.jsonを置き、モジュール名や名前空間を設定します。
{
"$schema": "https://nitro.margelo.com/nitro.schema.json",
"cxxNamespace": ["math"],
"ios": {
"iosModuleName": "NitroMath"
},
"android": {
"androidNamespace": ["math"],
"androidCxxLibName": "NitroMath"
},
"autolinking": {}
}
あとはコマンドを1つ実行するだけです。
npx nitrogen
./nitrogen/generated/配下に、Swiftのプロトコル、Kotlinの抽象クラス、C++のバインディングコードが生成されます。
3. ネイティブ側を実装する
生成されたSpecに準拠するクラスを、SwiftとKotlinでそれぞれ実装します。
// iOS: HybridMath.swift
class HybridMath: HybridMathSpec {
var pi: Double {
return Double.pi
}
func add(a: Double, b: Double) -> Double {
return a + b
}
}
// Android: HybridMath.kt
class HybridMath : HybridMathSpec() {
override var pi: Double
get() = Double.PI
override fun add(a: Double, b: Double): Double {
return a + b
}
}
TypeScriptのnumberがSwiftではDouble、KotlinでもDoubleとして型付けされていることに注目してください。型変換の対応表を暗記する必要はなく、生成されたSpecに従うだけで型が揃います。
4. JavaScriptから呼び出す
実装したHybridObjectは、通常のJavaScriptオブジェクトと同じ感覚で使えます。
import { NitroModules } from 'react-native-nitro-modules'
import type { Math } from './Math.nitro'
const math = NitroModules.createHybridObject<Math>('Math')
console.log(math.pi) // 3.141592653589793
console.log(math.add(5, 7)) // 12
math.add(5, 7)という呼び出しは、ブリッジのシリアライズを経由せず、JSIを通じてほぼ直接ネイティブ関数を実行します。これがNitroの速さの正体です。
実践的なユースケース
非同期処理とPromise
重い処理をネイティブ側で実行し、結果をPromiseで返すパターンは実務で頻出です。NitroではTypeScriptのPromiseをそのまま宣言できます。
// ImageProcessor.nitro.ts
import type { HybridObject } from 'react-native-nitro-modules'
interface ImageProcessor
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
/** 画像をリサイズして保存先パスを返す */
resize(path: string, width: number, height: number): Promise<string>
}
Swift側ではasync関数として自然に実装できます。
class HybridImageProcessor: HybridImageProcessorSpec {
func resize(path: String, width: Double, height: Double) -> Promise<String> {
return Promise.async {
// 重いリサイズ処理をバックグラウンドで実行
let resizedPath = try await ImageResizer.resize(
at: path,
to: CGSize(width: width, height: height)
)
return resizedPath
}
}
}
呼び出し側は普通の非同期関数と変わりません。
const processor = NitroModules.createHybridObject<ImageProcessor>('ImageProcessor')
const thumbnailPath = await processor.resize('/path/to/photo.jpg', 320, 240)
console.log(`サムネイルを保存しました: ${thumbnailPath}`)
コールバックでネイティブのイベントを受け取る
センサー値の監視のように、ネイティブ側から継続的に通知を受けたい場合は、コールバック関数を渡せます。
// StepCounter.nitro.ts
import type { HybridObject } from 'react-native-nitro-modules'
interface StepCounter
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
startCounting(onStep: (count: number) => void): void
stopCounting(): void
}
const counter = NitroModules.createHybridObject<StepCounter>('StepCounter')
counter.startCounting((count) => {
console.log(`現在の歩数: ${count}`)
})
// 不要になったら停止
counter.stopCounting()
従来のNativeEventEmitterのような文字列ベースのイベント購読と違い、コールバックの引数まで型チェックされるのが大きな安心材料です。
エコシステムでの採用実績
Nitro Modulesはすでに実戦投入が進んでおり、react-native-mmkv(高速ストレージ)やreact-native-unistyles(スタイリング)といった人気ライブラリが新バージョンでNitroベースへ移行しています。「Nitroで作られたライブラリを使う」だけなら、コアパッケージを入れるだけで恩恵を受けられます。
まとめ
Nitro Modulesの要点を振り返ります。
- TypeScriptのインターフェース定義から、Nitrogenが型安全なSwift/Kotlin/C++コードを自動生成します
- 静的コンパイルされたJSIバインディングにより、従来のブリッジ方式を大きく上回る速度で動作します
- Promise、コールバック、オブジェクトなど、JavaScriptの型をほぼそのまま扱えます
「ネイティブモジュール開発はつらい」という常識は、もはや過去のものになりつつあります。まずは既存アプリのreact-native-mmkvをNitro版に上げてその速さを体感し、次の週末には.nitro.tsファイルを1つ書いてみてください。TypeScriptの型がそのままSwiftのプロトコルになる瞬間は、ちょっとした感動ですよ。
公式ドキュメントはnitro.margelo.com、ソースコードはGitHubで公開されています。
