React Nativeでクロスプラットフォームアプリを開発するメリットと実践知見
はじめに
N.N. LLC.では、モバイルオーダーシステム、デジタルスタンプカードアプリ、ポイント管理アプリなど、複数のReact Nativeプロジェクトを手がけてきました。また、iOS向けのネイティブアプリをSwiftで開発する案件も並行して進めています。
本記事では、これらの実践経験から得たReact Nativeのメリット・デメリット、技術選定の判断基準、そしてパフォーマンス最適化のノウハウを包括的に解説します。React NativeかFlutterか、あるいはネイティブ開発かで迷っている方の判断材料になれば幸いです。
React Native 0.73+の新アーキテクチャ
React Nativeは2024年以降、新アーキテクチャ(New Architecture)への移行が本格化しました。従来のBridge方式から大きく進化し、パフォーマンスと開発体験が向上しています。
Fabric — 新しいレンダリングシステム
FabricはReact Nativeの新しいレンダリングエンジンで、以下の改善をもたらします。
- 同期的なレイアウト計算: 従来の非同期Bridgeを介さず、JavaScriptとネイティブUI間のレイアウトを同期的に処理。スクロール中のちらつきや遅延が大幅に改善
- コンカレントレンダリング: React 18のConcurrent Featuresに対応し、優先度に基づいたレンダリングが可能に
- 型安全なネイティブコンポーネント: CodeGenにより、JavaScript側の型定義からネイティブコードを自動生成
TurboModules — 高速なネイティブモジュール
TurboModulesは、従来のNative Modulesの後継です。遅延読み込み(Lazy Loading)に対応し、アプリ起動時に不要なモジュールをロードしないため、起動時間が改善されます。
// TurboModule仕様の定義例(TypeScript)
import type { TurboModule } from "react-native"
import { TurboModuleRegistry } from "react-native"
// ネイティブモジュールのインターフェース定義
export interface Spec extends TurboModule {
// カメラモジュールの例
openCamera(options: CameraOptions): Promise<PhotoResult>
getPhotoLibrary(limit: number): Promise<Photo[]>
}
// 遅延読み込みされるTurboModule
export default TurboModuleRegistry.getEnforcing<Spec>(
"CameraModule"
)
Bridgelessモード
React Native 0.73以降では、従来のBridgeを完全に排除したBridgelessモードが利用可能です。JavaScript ↔ ネイティブ間の通信がJSI(JavaScript Interface)経由になり、シリアライゼーション/デシリアライゼーションのオーバーヘッドがなくなります。
Expo vs Bare Workflow — 選択基準
React Nativeのプロジェクトセットアップには、大きく分けてExpo(Managed Workflow)とBare Workflowの2つの選択肢があります。
Expoを選ぶべきケース
- プロトタイプやMVPの高速開発(カメラ、プッシュ通知、位置情報など標準的な機能で十分)
- ネイティブ開発の経験が少ないチーム
- OTAアップデート(EAS Update)を積極的に活用したい場合
- Expo SDKが提供するライブラリで要件を満たせる場合
Bare Workflowを選ぶべきケース
- カスタムネイティブモジュールの実装が必要な場合(Bluetooth LE、独自SDKの組み込みなど)
- アプリサイズの厳密な管理が求められる場合
- ネイティブ依存のサードパーティSDK(決済SDK、地図SDKなど)の統合が必要な場合
- Xcodeプロジェクトやbuild.gradleを直接カスタマイズする必要がある場合
Expo + Development Build(推奨パターン)
最近のExpoは「Expo Modules」と「Development Build」の導入により、Managed WorkflowとBare Workflowの境界が曖昧になっています。Expoの快適な開発体験を維持しつつ、必要に応じてカスタムネイティブコードを追加できるこのアプローチが、現時点でのベストプラクティスです。
// expo-modules.config.json でカスタムネイティブモジュールを統合
{
"ios": {
"modules": ["./modules/custom-camera"]
},
"android": {
"modules": ["./modules/custom-camera"]
}
}
iOS/Android共通コードの実際の割合
「React Nativeでどのくらいコードを共通化できるのか」は、最も多い質問の一つです。N.N. LLC.の実案件での実測値を紹介します。
共通コード率の実績
- モバイルオーダーシステム: 共通コード率 88%。プラットフォーム固有コードは主にプッシュ通知の設定とネイティブの決済UI
- デジタルスタンプカードアプリ: 共通コード率 85%。QRコード読み取りのカメラ設定がiOS/Androidで異なる
- ポイント管理アプリ: 共通コード率 92%。ほぼ全機能をReact Nativeで実装。ネイティブコードはアプリアイコンと起動画面のみ
平均すると、85-90%のコードがiOS/Androidで共通です。残りの10-15%は、プラットフォーム固有のUIカスタマイズやネイティブ機能の呼び出し部分です。
プラットフォーム固有コードの実装パターン
// Platform.selectによるプラットフォーム分岐
import { Platform, StyleSheet } from "react-native"
const styles = StyleSheet.create({
// iOSとAndroidで異なるシャドウ表現
card: {
...Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
},
android: {
elevation: 4,
},
}),
},
})
// ファイル名によるプラットフォーム分岐
// Button.ios.tsx → iOSでのみ読み込まれる
// Button.android.tsx → Androidでのみ読み込まれる
React Native vs Flutter vs ネイティブ開発の比較
クロスプラットフォーム開発の選択肢として、React Native、Flutter、ネイティブ開発を比較します。
開発言語と学習コスト
- React Native: JavaScript/TypeScript。Web開発者なら比較的低い学習コストで参入可能
- Flutter: Dart。Dart自体の学習が必要だが、言語設計は現代的で習得しやすい
- ネイティブ: Swift(iOS)+ Kotlin(Android)。2言語の習得が必要だが、各プラットフォームの最新機能に即座にアクセス可能
パフォーマンス
- React Native: 新アーキテクチャ(Fabric + TurboModules)により大幅改善。一般的なビジネスアプリでは十分な性能
- Flutter: Skiaレンダリングエンジンによる高い描画性能。60fps/120fpsのアニメーションが安定的に実現可能
- ネイティブ: 最高のパフォーマンス。ゲームやAR/VRアプリなど高負荷処理では依然としてネイティブが有利
エコシステムとコミュニティ
- React Native: npmの巨大なエコシステムを活用可能。Meta(Facebook)が主導開発。コミュニティが非常に活発
- Flutter: Googleが主導開発。pub.devのパッケージエコシステムが急成長中
- ネイティブ: Apple/Googleの公式SDKとドキュメントが最も充実。新しいOS機能への対応が最速
N.N. LLC.の技術選定ガイドライン
当社では、以下の基準でプロジェクトごとに技術を選定しています。
- React Native: Web技術者が中心のチーム、ビジネスアプリ、短期開発、予算重視のプロジェクト
- Swift(ネイティブiOS): iOS限定のゲーム、AR/VR対応アプリ、iOS固有機能を最大限活用するアプリ
- Flutter: UIの一貫性が最重要、カスタムアニメーションが多いアプリ、Dartに習熟したチームがいる場合
パフォーマンス最適化の実践テクニック
FlatListの最適化
大量のアイテムを表示するリストは、FlatListの適切な設定が不可欠です。
// FlatListの最適化設定
<FlatList
data={products}
renderItem={renderProduct}
keyExtractor={(item) => item.id}
// ビューポート外のアイテムを削除してメモリ節約
removeClippedSubviews={true}
// 表示領域の前後にレンダリングするアイテム数
windowSize={5}
// 初期表示するアイテム数
initialNumToRender={10}
// バッチレンダリングのアイテム数
maxToRenderPerBatch={5}
// スクロール感度の調整
updateCellsBatchingPeriod={50}
// 各アイテムの高さが固定の場合に指定(スクロール計算の高速化)
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
メモリ管理
- 画像のキャッシュ:
react-native-fast-imageを使い、画像のメモリキャッシュとディスクキャッシュを管理。同一画像の再ダウンロードを防止 - メモ化:
React.memo、useMemo、useCallbackを適切に使い、不要な再レンダリングを防止 - メモリリークの検出: Flipperのメモリプロファイラーで定期的にメモリ使用量を監視
Hermesエンジンの活用
Hermesは、React Native向けに最適化されたJavaScriptエンジンです。React Native 0.70以降ではデフォルトで有効になっています。
- 起動時間の短縮: バイトコードへの事前コンパイルにより、JavaScriptの解析時間を削減。起動時間が30-50%短縮
- メモリ使用量の削減: 効率的なガベージコレクションにより、メモリ使用量が10-30%改善
- APK/IPAサイズの削減: エンジン自体のバイナリサイズが軽量
開発フローとCI/CD
開発ワークフロー
- 機能ブランチの作成: Gitで機能ブランチを切り、プルリクエストベースで開発
- Hot Reloadでの開発: コード変更が即座にシミュレーター/実機に反映。UIの調整が高速
- TypeScriptでの型チェック: コンパイル時に型エラーを検出し、ランタイムエラーを未然に防止
- ESLint + Prettier: コードスタイルの統一。プルリクエスト時にCIで自動チェック
- Jestによるテスト: ユニットテストとスナップショットテストを実行
CI/CDパイプライン
EAS(Expo Application Services)またはFastlaneを使い、以下のCI/CDパイプラインを構築しています。
- PRマージ → テスト自動実行: Jest + React Native Testing Libraryでユニットテスト・統合テストを実行
- mainブランチへのマージ → ステージングビルド: TestFlight(iOS)/ Firebase App Distribution(Android)に自動デプロイ
- リリースタグの作成 → プロダクションビルド: App Store / Google Playへの自動申請
- OTAアップデート: JavaScriptバンドルの変更のみの場合、EAS UpdateでApp Storeの審査なしに即時配信
まとめ
React Nativeは、新アーキテクチャの導入により大きく進化し、ビジネスアプリケーションの開発においてネイティブ開発と遜色ないパフォーマンスを実現できるようになりました。特に、Web技術者のスキルセットを活かせる点、85-90%のコード共通化によるコスト削減、そしてExpoエコシステムの充実が大きな強みです。
一方で、高負荷なゲームやAR/VR対応アプリ、プラットフォーム固有のUI/UXを追求する場合は、ネイティブ開発を選択するのが適切です。N.N. LLC.では、プロジェクトの要件に応じてReact Native、Swift、Kotlinを使い分け、最適な技術選定を行っています。