react-native-advanced について
React Native と Expo の開発者向けスキルです。TanStack、XState、Zustand のネイティブ向けパターンや連携方法、注意点をまとめています。
主な機能
- 01ネイティブ向け React Query 連携ルール
- 02Expo Router のナビゲーションと認証パターン
- 03FlashList の無限スクロール実践
- 04MMKV の永続化と暗号化ストレージ
- 05アニメーション、ジェスチャー、更新の指針
活用シーン
- 01AppState と NetInfo でクエリマネージャーを設定
- 02Expo Router の画面向けデータの先読みと更新
- 03FlashList によるページ分割リストの構築
- 04アニメーション、通知、OTA 更新への対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill react-native-advancedインストール後、Claude Code で/react-native-advancedを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/animations.mdbottom-sheet.mdexpo-essentials.mdexpo-router.mdlists.mdnotifications.mdreact-query-rn.mdtesting-rn.mdzustand-rn.md詳細
React Native Advanced:Expo + TanStack/XState/Zustand エコシステム
TanStack エコシステム、XState、Zustand を使って構築するアプリ向けの React Native と Expo のパターンです。このスキルは、react-advanced(クロスプラットフォームの基本パターン)を拡張します。React Query、XState、Zustand、Zod、TanStack Form、TanStack Table の規約については、まずそのスキルをお読みください。
RN アーキテクチャ
ライブラリの対応関係は、Web と React Native で異なります。
| Web ライブラリ | RN での同等機能 | 主な違い |
|---|---|---|
| TanStack Router | Expo Router | ネイティブではルートローダーがなく、ファイルベースのナビゲーションです |
| TanStack Start | — | ネイティブでは SSR/サーバー関数がありません |
| TanStack Virtual | FlashList | DOM の仮想化ではなく、ネイティブのビュー再利用です |
| localStorage | MMKV | 同期的で、ネイティブスレッド上で動作し、30 倍高速です |
| window events | AppState/NetInfo | フォーカス/オンラインマネージャーとの手動連携が必要です |
| CSS animations | Reanimated | UI スレッドのワークレット、CSS トランジション(v4) |
| DOM events | Gesture Handler | ジェスチャー合成 API、UI スレッドのコールバック |
<img> | expo-image | SDWebImage/Glide、blurhash、ディスクキャッシュ |
| Web Push API | expo-notifications | FCM/APNs、チャンネル、バックグラウンドタスク |
| Service Workers | expo-updates | OTA アップデート、段階的なロールアウト、緊急ロールバック |
クロスプラットフォームライブラリ(Web と RN で API は同一): React Query、XState、Zustand、Zod、TanStack Form、TanStack Table
必須のセットアップ
2 つの連携が必須です。これらがないと、React Native では React Query の自動再フェッチとオフライン処理が機能しません。
- focusManager —
AppStateをfocusManager.setFocused()に連携し、アプリがフォアグラウンドに戻ったときにrefetchOnWindowFocusが機能するようにします。これがない場合、デフォルト設定は通知なく無視されます。 - onlineManager —
NetInfoをonlineManager.setOnline()に連携し、接続状況の変化に応じてクエリを一時停止/再開できるようにします。
QueryClientProvider 内のルート _layout.tsx で、両方のフックを一度呼び出してください。
実装全体については references/react-query-rn.md を参照してください。
ルートローダーを使わないデータ取得
Expo Router にはネイティブのルートローダーがありません(データローダーは Web 専用/アルファ版です)。パターンは、ユーザー操作時にプリフェッチし、遷移先の画面でデータを利用することです。
ルール:
router.pushの前にprefetchQueryをawaitせずに実行します。awaitするとナビゲーションが遅くなります。- 画面が再びフォーカスを得たときに古いデータを無効化するには、
useFocusEffect(expo-routerからインポート)を使います。画面はマウントされたままになるため、戻るナビゲーションではuseEffectは再実行されません。 refetch()(常に再フェッチ)ではなく、invalidateQueries(staleTimeを尊重)を使います。
プリフェッチと useRefreshOnFocus のパターンについては references/react-query-rn.md を参照してください。
ナビゲーションと認証
Stack.Protected(Expo Router v5 以降、推奨)
// app/_layout.tsx
export default function RootLayout() {
const session = useAuthStore((s) => s.session)
return (
<Stack>
<Stack.Protected guard={!!session}>
<Stack.Screen name="(tabs)" />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack.Protected>
<Stack.Protected guard={!session}>
<Stack.Screen name="sign-in" />
</Stack.Protected>
</Stack>
)
}
session が切り替わると、Expo Router は自動的にリダイレクトし、履歴をクリーンアップします。トークン確認、更新、エラー復旧などの複雑な認証フローでは、XState で認証状態を管理し、guard プロパティ用のブール値を導出します。
XState を使った認証の実装例と Zustand の認証ストアパターンについては references/expo-router.md を参照してください。
リスト:FlashList + React Query
FlashList は RN 向けに TanStack Virtual を置き換えます。DOM ベースの絶対位置指定ではなく、ネイティブのビュー再利用を使用します。
ルール:
onEndReached内の!isFetchingNextPageガードは必須です。FlashList は短時間にonEndReachedを複数回発火させることがあり、重複したフェッチが発生します。- フラット化した
items配列はuseMemoでメモ化してください。data.pages.flatMap()はレンダーごとに新しい参照を作成します。 handleEndReachedはuseCallbackで安定させてください。- 次のページがないことを示すには、
getNextPageParamはnullではなくundefinedを返す必要があります。
無限スクロールの完全なパターン、FlashList v2 の変更点、パフォーマンスの調整については、references/lists.md を参照してください。
RN での TanStack Form
TanStack Form はヘッドレスで、DOM 依存もアダプターも不要です。Web との主な違いは次のとおりです。
TextInputはonChange(イベントオブジェクト)ではなく、onChangeText(文字列を直接渡す)を使用します。field.handleChangeをonChangeTextに直接接続してください。- 数値フィールドでは、呼び出し側で変換してください。
onChangeText={(val) => field.handleChange(val === '' ? null : Number(val))}。 - フォームは
keyboardShouldPersistTaps="handled"を指定したScrollViewで囲んでください。そうしないと、Submit の最初のタップで押下イベントが発火せず、キーボードが閉じます。
MMKV を使った Zustand Persist
MMKV は同期的に動作し、ネイティブスレッドで実行されるため、非同期のハイドレーション待ち時間はありません。MMKV インスタンスはモジュールレベルで作成してください(コンポーネント内では決して作成しないでください)。StateStorage アダプターは、キーが存在しない場合に null を返す必要があります:mmkv.getString(name) ?? null。
機密データには、encryptionKey を指定した暗号化 MMKV インスタンスを使用してください。ハイブリッドパターン(ハードウェアで保護されたキー + 暗号化 MMKV)については、references/expo-essentials.md を参照してください。
アダプター、ストアの作成、暗号化ストレージ、ハイドレーションのタイミング、バニラストアのパターンについては、references/zustand-rn.md を参照してください。
アニメーションとジェスチャー
Reanimated はワークレットを介して UI スレッド 上でアニメーションを実行します。Gesture Handler はタッチイベントを同じスレッドにルーティングします。Reanimated 4 では、CSS スタイルの宣言的なトランジションが追加されています。
重要なルール
- 値全体を置き換える:
sv.value.x = 50ではなくsv.value = { x: 50 }を使ってください(リアクティビティが壊れます) - 分割代入しない:
const { x } = sv.valueはリアクティブな値ではなく、通常の数値を作成します - レンダー中に読み取らない:共有値の読み取りは副作用であり、React のルールに違反します
- 共有値では再レンダーされない:React の反応が必要な場合は、別の状態を保持してください
- GestureHandlerRootView は
style={{ flex: 1 }}を指定してアプリのルートを囲む必要があります - Android のモーダル には独自の
GestureHandlerRootViewが必要です(ネイティブのルートビューの外側)
スレッド処理の概要
共有値は UI スレッド上にあります。JS スレッド上で .value を読み取ると、ブリッジ呼び出しで処理がブロックされるため、負荷の高い処理では決して行わないでください。ジェスチャーコールバックと useAnimatedStyle は UI スレッドで実行されます。JS 関数を呼び出すには、runOnJS(Reanimated 3)または scheduleOnRN(Reanimated 4)を使用してください。
宣言的アニメーションと命令的アニメーション
状態に応じたスタイルの変更(色、透明度、寸法の切り替え)には CSS トランジション(Reanimated 4)を使用してください。マウント/アンマウントには レイアウトアニメーション(entering/exiting props)を使用してください。ジェスチャー駆動のアニメーションや命令的な連鎖には ワークレット + 共有値 を使用してください。
詳細なパターン、Reanimated 4 の API 変更、ジェスチャーの組み合わせ、CSS トランジションについては、references/animations.md を参照してください。
ボトムシート
@lodev09/react-native-true-sheet は、UISheetPresentationController(iOS)および BottomSheetDialog(Android)を基盤とするネイティブのボトムシートです。Reanimated への依存はありません。新アーキテクチャ専用です。
主なルール
- 命令型の制御のみ — 状態プロパティではなく、
ref.present()/dismiss()/resize()を使用してください。 - デテントは最大3つとし、小さい順に並べてください。コンテンツに合わせるには
'auto'を使用します。 scrollableと'auto'デテントを併用しないでください — 互いに競合します。固定デテントを使用してください。- シートのコンテンツに
flex: 1を使用しないでください — 高さがゼロに縮みます。flexGrowまたは固定の高さを使用してください。 - アンマウントする前に必ず
dismiss()を呼び出してください — ネイティブのシートはReactコンポーネントより長く存続します。 - 標準の
TextInputが使用できます — 特別なキーボードコンポーネントは不要です(ネイティブで処理されます)。 - 標準の
ScrollView/FlashListが使用できます。scrollableとnestedScrollEnabledを組み合わせてください — v3では自動検出されます(最大2階層)。
パターン、プラットフォームごとの差異、Expo Routerとの連携について詳しくは、references/bottom-sheet.mdを参照してください。
プッシュ通知
expo-notificationsは、Expoのプッシュサービスを通じてFCM(Android)とAPNs(iOS)を処理します。
SDK 53の破壊的変更
- Androidのプッシュ通知はExpo Goでは動作しません — 開発ビルドが必要です。
app.jsonのplugins配列でConfig Pluginを明示する必要があります。
主なパターン
// Must configure or foreground notifications are silently suppressed
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
通知のルール
- Android 13以降では、権限をリクエストする前にAndroidのチャンネルを作成する必要があります。
- チャンネルは変更できません — 作成後に重要度やバイブレーションを変更することはできません。
- 本番環境ではレシートをポーリングしてください — チケットで確認できるのは、Expoがリクエストを受信したことだけです。
- バックグラウンドタスクはモジュールスコープで定義する必要があります(コンポーネント内では定義しないでください)。
権限の処理、リスナー、バックグラウンドタスクについては、references/notifications.mdを参照してください。
OTAアップデート
expo-updatesを使用すると、アプリストアでリリースすることなく、JavaScriptバンドルをOTAで更新できます。
アップデート確認のパターン
import * as Updates from "expo-updates";
// Run after first render, not during startup (can freeze UI on slow network)
const check = await Updates.checkForUpdateAsync();
if (check.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
緊急起動の検出
if (Updates.isEmergencyLaunch) {
// OTA caused a crash — app rolled back to embedded bundle
// Log immediately to error tracking
}
必ずこれを計測してください。OTAアップデートにおける本番環境のクラッシュシグナルになります。
段階的なロールアウト、ランタイムバージョン、リアクティブなパターンについては、references/expo-essentials.mdを参照してください。
ファイル構成
構成:app/には、各セグメントに対応する_layout.tsxを含むルートファイルを配置します。ルートグループ(auth)/、(app)/、(tabs)/は、URLに影響を与えずにファイルを整理します。機能コードはapp/の外に配置します:queries/(フックではなくqueryOptionsオブジェクト)、mutations/、machines/(Reactのインポートを含まない純粋なTS)、stores/(Zustand + MMKV persist)、hooks/、components/。
よくある落とし穴
-
focusManager/onlineManager のセットアップ漏れ — 手動で AppState と NetInfo を連携しないと、
refetchOnWindowFocusとオフライン時の一時停止は機能しません。 -
router.pushの前にprefetchQueryを await する — 画面遷移が遅く感じられます。await せずに先読みを開始し、React Query のキャッシュから遷移先の画面にデータを提供してください。 -
useLocalSearchParamsではなくuseGlobalSearchParamsを使う — ナビゲーションイベントのたびにグローバルに再レンダリングされます。常にローカルを優先してください。 -
画面のフォーカス時の再取得に
useEffectを使う — 画面に戻ったとき(画面はマウントされたままです)にuseEffectは再実行されません。expo-routerのuseFocusEffectを使ってください。 -
getNextPageParamがnullを返す — 次のページがないことを示すにはundefinedを返す必要があります。API の null 値はlastPage.nextCursor ?? undefinedのように変換してください。 -
FlashList v2 には New Architecture が必要 — v2 は Fabric 向けに一から書き直されています。
estimatedItemSizeは非推奨です(無視されます)。overrideItemLayoutがサポートするのはspanのみです。 -
MMKV の
getStringがundefinedを返す — Zustand のStateStorage.getItemは、キーが存在しない場合にnullを返す必要があります。常に?? nullで変換してください。 -
コンポーネント内で MMKV インスタンスを作成する — レンダリングのたびに新しいインスタンスが作成されます。モジュールレベルで宣言してください。
-
フォームの ScrollView で
keyboardShouldPersistTapsが未設定 — 最初のタップで Submit ボタンが押されず、キーボードが閉じてしまいます。 -
モバイルで
retry: 3(デフォルト)を使用する — 接続が不安定な場合、指数バックオフによる 3 回の再試行で 30 秒以上かかることがあります。時間に敏感な UI ではretry: 1を検討してください。 -
共有値のプロパティを変更する —
sv.value.x = 50はリアクティビティを壊します。値全体を置き換える必要があります:sv.value = { x: 50, y: 0 }。 -
GestureHandlerRootView に
flex: 1がない — ラッパーの高さがゼロになります。ジェスチャーがまったく機能しないように見えます。 -
開いたままの
TrueSheetをアンマウントする — ネイティブシートは自動的に閉じません。ツリーから削除する前に、必ずdismiss()を呼び出してください。 -
TrueSheet で
scrollableとautoのデテントを併用する — 自動サイズ調整とスクロールの固定が競合します。scrollable={true}の場合は固定デテントを使ってください。 -
setNotificationHandlerの設定漏れ — フォアグラウンド通知はすべて通知されずに抑制されます。エラーは発生しません。 -
低速なネットワークで
checkForUpdateAsyncを実行する — Android の UI が数分間フリーズすることがあります。初回レンダリング後に、手動でタイムアウトを設定して実行してください。 -
FlashList で expo-image の
recyclingKeyを設定しない — 設定しないと、再利用されたセルに前のアイテムの画像が一瞬表示されます。 -
expo-secure-store の生体認証による無効化 —
requireAuthenticationを指定したキーは、生体認証情報が変更されると永久に読み取れなくなります。アプリデータの消去以外に復旧方法はありません。
リファレンスファイル
| ファイル | 読むタイミング |
|---|---|
references/react-query-rn.md | フォーカス/オンラインマネージャー、キャッシュの永続化、先読み |
references/expo-router.md | 型付きルート、レイアウト、モーダル、認証、検索パラメーター |
references/lists.md | FlashList と React Query、無限スクロール、パフォーマンス |
references/zustand-rn.md | MMKV 永続化アダプター、暗号化、ハイドレーションのパターン |
references/testing-rn.md | RNTL、Query/Router/Form/XState のテスト、RN での MSW |
references/animations.md | Reanimated、Gesture Handler のパターンと注意点 |
references/bottom-sheet.md | react-native-true-sheet のセットアップ、デテント、Expo Router |
references/notifications.md | expo-notifications の権限、リスナー、バックグラウンド処理 |
references/expo-essentials.md | expo-image、expo-secure-store、expo-haptics、expo-updates |