パフォーマンス
RN は2つのスレッド(JS と UI)の上で動く。JS スレッドが詰まるとアニメが落ちるのが基本構造。 実機(特にローエンド Android)でテストし、JS スレッドを軽く保つことが要。
2 つのスレッド
- JS スレッド — React の reconciliation、イベント処理、データ取得など
- UI スレッド(Main) — レンダリング、レイアウト、ジェスチャ認識、アニメ反映
重い計算を JS スレッドで回すと、UI スレッドへの指示が遅れてカクつく。
Reanimated や Animated(useNativeDriver)が有効な場合は、UI スレッド単独でアニメが続くので JS スレッドが詰まっても止まらない。
JS エンジン: Hermes
Facebook 製の RN 専用 JS エンジン。2026 現在は Expo / RN ともにデフォルト。 起動時間とメモリで JSC(旧来の JavaScriptCore)より優位。
- 有効化:
app.jsonのjsEngine: "hermes"(既定)。 - 確認:
typeof HermesInternal !== "undefined"。
New Architecture(Fabric / TurboModules)
RN の刷新された内部アーキテクチャ。2026 現在は Expo / RN のデフォルト。
- Fabric — 新しい UI レンダラ。同期的に呼べる、より型安全。
- TurboModules — ネイティブモジュールのアクセスを高速化。
- JSI(JavaScript Interface) — JS から C++ オブジェクトを直に呼べる仕組み。
既存の古いライブラリは New Architecture 非対応の可能性あり。新規ライブラリは原則対応しているかをチェック。
FlatList の最適化
リストはパフォーマンスの最大の悩みどころ。
必須プロパティ
keyExtractor— 安定した一意キー(インデックスではなくid)renderItem— 関数をuseCallbackで安定化getItemLayout— 行高さが固定なら指定すると劇的に速くなる
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
調整 props
initialNumToRender— 初期描画する件数(既定 10)maxToRenderPerBatch— 1 バッチで描く件数windowSize— 画面外に保持するページ数(既定 21)removeClippedSubviews— 画面外を実際にビューから外す(Android で効果大)updateCellsBatchingPeriod— バッチ周期
各セルの最適化
- セルコンポーネントを
React.memo renderItem内で新しい関数や配列を作らない(再レンダー誘発)- 画像は
expo-imageでキャッシュ + プレースホルダ
FlashList(Shopify)
FlatList より遥かに速い代替実装。estimatedItemSize を渡すだけ。
import { FlashList } from "@shopify/flash-list"
<FlashList
data={items}
estimatedItemSize={64}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
画像
expo-image: キャッシュ・プレースホルダ・遷移付き。標準Imageより速い。- 適切なサイズで読む: 元画像が 4000px ある時に 80px で表示しない。サーバ側で縮小、または
?width=80等。 - blurhash や色プレースホルダで、ロード中の画面を埋める。
- WebP / AVIF を使う(Hermes は両方対応)。
React の最適化
不要な再レンダーを抑える
React.memo— props 変化時だけ再レンダーuseCallback— 関数の参照を安定化(memo した子に渡す時)useMemo— 重い計算結果のキャッシュ- Zustand のセレクタを使って必要な部分だけ購読
React Compiler
React 19 系から導入された自動メモ化コンパイラ。手書きの memo / useCallback / useMemo がほぼ不要になる。
Expo Router の最新テンプレートでは段階的に導入されている。
大きな state を分割する
1つの巨大 Context や Zustand store にすべて入れると、1 箇所の変更で全体が再レンダーされる。 セレクタや atom 単位の管理(Jotai / Zustand selector)で局所化する。
アニメーションを UI スレッドで動かす
- Animated 標準 →
useNativeDriver: trueを必ず(transform / opacity 限定) - Reanimated → SharedValue + worklet で UI スレッド完結
- Gesture Handler → ジェスチャ認識もネイティブ側で完結
JS スレッドが詰まっても、ネイティブ駆動のアニメは止まらない。ローディング中もスムーズな見た目を保てる。
起動時間
- Hermes: 起動が JSC より速い
- RAM Bundles + Inline Requires: アプリ起動時に全コードを評価せず、必要時に loadする
- 初期画面を軽く: ホーム画面で重い API を全部叩かない、優先度を分ける
- SplashScreen:
expo-splash-screenでフォントや必要なデータをロードしてから表示
メモリ
- 大きな画像を多数キャッシュしない(
expo-imageのcachePolicy調整) - FlatList の
removeClippedSubviewsを有効化 - 音声・動画のリソースを使い終わったら解放(
player.release()) - Reanimated の SharedValue は明示的に破棄不要だがリスナー(
cancelAnimation)は止める
ネットワーク
- キャッシュは React Query 任せ: staleTime / cacheTime を適切に
- オフライン対応:
networkMode: "offlineFirst"でキャッシュ表示優先 - 画像 / API の並列リクエスト数を制限: 一度に大量で帯域を食い合わない
- Long Polling や WebSocket はバックグラウンドで切断される前提に
プロファイリングツール
標準
- Performance Monitor — 開発メニューから ON。FPS(JS / UI)が右上に表示される。
- React DevTools —
npx react-devtools。Profiler タブで再レンダー追跡。 - Hermes Profiler — JS スレッドのフレームをサンプリング。
サードパーティ
- Flipper(既存プロジェクト): 廃止方向。Expo は対応していない。
- Expo Dev Tools: ターミナルや Web UI から確認。
- Sentry: クラッシュとパフォーマンス監視。本番に必須レベル。
- react-native-performance: Web Vitals 風のメトリクス。
iOS の Instruments / Android の Profiler
ネイティブレイヤーの調査には Xcode / Android Studio の付属プロファイラ。JS で対処できない問題を追う時に。
計測の手順
- 実機(できればローエンド Android)でテスト。シミュレータは速すぎてあてにならない。
- 本番ビルド(
npx expo run:ios --configuration Release等)で計測。dev は遅い。 - Performance Monitor を ON、操作中に FPS が落ちる場面を特定。
- その場面の JS スレッド負荷を Hermes Profiler でサンプリング。
- 怪しいコンポーネントを React DevTools Profiler でレンダー回数チェック。
- 問題箇所を memo / useCallback / リスト最適化 / native driver で改善。
本番でのモニタリング
- Sentry: クラッシュ + Performance(トランザクション)
- Datadog RUM: モバイル RUM
- Firebase Crashlytics: クラッシュレポート
- EAS Insights: Expo 標準のクラッシュ・利用状況分析(限定的)
判断ガイド
| 症状 | 疑うべき原因 |
|---|---|
| リストが重い・カクつく | FlatList → FlashList、getItemLayout、セルの memo |
| アニメがガタつく | useNativeDriver または Reanimated に |
| 起動が遅い | 初期化処理が重い、画像が大きすぎる、フォントが多い |
| メモリ警告 | 画像キャッシュ、FlatList 設定、メディア解放忘れ |
| JS スレッド詰まり | 無駄な再レンダー、重い計算が同期実行 |
✓ Hermes ON(既定)
✓ New Architecture ON(既定)
✓ FlatList → FlashList 検討
✓ Reanimated でアニメ
✓ expo-image + 適切な解像度
✓ 実機(ローエンド Android)で本番ビルドを確認