mcpField
R
react-native-advanced

react-native-advanced

github.com/trancong12102開発748 回インストール5 週間前に更新
リポジトリ

react-native-advanced について

React Native と Expo の開発者向けスキルです。TanStack、XState、Zustand のネイティブ向けパターンや連携方法、注意点をまとめています。

主な機能

  1. 01ネイティブ向け React Query 連携ルール
  2. 02Expo Router のナビゲーションと認証パターン
  3. 03FlashList の無限スクロール実践
  4. 04MMKV の永続化と暗号化ストレージ
  5. 05アニメーション、ジェスチャー、更新の指針

活用シーン

  1. 01AppState と NetInfo でクエリマネージャーを設定
  2. 02Expo Router の画面向けデータの先読みと更新
  3. 03FlashList によるページ分割リストの構築
  4. 04アニメーション、通知、OTA 更新への対応

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trancong12102/agentskills --skill react-native-advanced

インストール後、Claude Code で/react-native-advancedを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
animations.md
bottom-sheet.md
expo-essentials.md
expo-router.md
lists.md
notifications.md
react-query-rn.md
testing-rn.md
zustand-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 RouterExpo Routerネイティブではルートローダーがなく、ファイルベースのナビゲーションです
TanStack Start—ネイティブでは SSR/サーバー関数がありません
TanStack VirtualFlashListDOM の仮想化ではなく、ネイティブのビュー再利用です
localStorageMMKV同期的で、ネイティブスレッド上で動作し、30 倍高速です
window eventsAppState/NetInfoフォーカス/オンラインマネージャーとの手動連携が必要です
CSS animationsReanimatedUI スレッドのワークレット、CSS トランジション(v4)
DOM eventsGesture Handlerジェスチャー合成 API、UI スレッドのコールバック
<img>expo-imageSDWebImage/Glide、blurhash、ディスクキャッシュ
Web Push APIexpo-notificationsFCM/APNs、チャンネル、バックグラウンドタスク
Service Workersexpo-updatesOTA アップデート、段階的なロールアウト、緊急ロールバック

クロスプラットフォームライブラリ(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/。


よくある落とし穴

  1. focusManager/onlineManager のセットアップ漏れ — 手動で AppState と NetInfo を連携しないと、refetchOnWindowFocus とオフライン時の一時停止は機能しません。

  2. router.push の前に prefetchQuery を await する — 画面遷移が遅く感じられます。await せずに先読みを開始し、React Query のキャッシュから遷移先の画面にデータを提供してください。

  3. useLocalSearchParams ではなく useGlobalSearchParams を使う — ナビゲーションイベントのたびにグローバルに再レンダリングされます。常にローカルを優先してください。

  4. 画面のフォーカス時の再取得に useEffect を使う — 画面に戻ったとき(画面はマウントされたままです)に useEffect は再実行されません。expo-router の useFocusEffect を使ってください。

  5. getNextPageParam が null を返す — 次のページがないことを示すには undefined を返す必要があります。API の null 値は lastPage.nextCursor ?? undefined のように変換してください。

  6. FlashList v2 には New Architecture が必要 — v2 は Fabric 向けに一から書き直されています。estimatedItemSize は非推奨です(無視されます)。overrideItemLayout がサポートするのは span のみです。

  7. MMKV の getString が undefined を返す — Zustand の StateStorage.getItem は、キーが存在しない場合に null を返す必要があります。常に ?? null で変換してください。

  8. コンポーネント内で MMKV インスタンスを作成する — レンダリングのたびに新しいインスタンスが作成されます。モジュールレベルで宣言してください。

  9. フォームの ScrollView で keyboardShouldPersistTaps が未設定 — 最初のタップで Submit ボタンが押されず、キーボードが閉じてしまいます。

  10. モバイルで retry: 3(デフォルト)を使用する — 接続が不安定な場合、指数バックオフによる 3 回の再試行で 30 秒以上かかることがあります。時間に敏感な UI では retry: 1 を検討してください。

  11. 共有値のプロパティを変更する — sv.value.x = 50 はリアクティビティを壊します。値全体を置き換える必要があります:sv.value = { x: 50, y: 0 }。

  12. GestureHandlerRootView に flex: 1 がない — ラッパーの高さがゼロになります。ジェスチャーがまったく機能しないように見えます。

  13. 開いたままの TrueSheet をアンマウントする — ネイティブシートは自動的に閉じません。ツリーから削除する前に、必ず dismiss() を呼び出してください。

  14. TrueSheet で scrollable と auto のデテントを併用する — 自動サイズ調整とスクロールの固定が競合します。scrollable={true} の場合は固定デテントを使ってください。

  15. setNotificationHandler の設定漏れ — フォアグラウンド通知はすべて通知されずに抑制されます。エラーは発生しません。

  16. 低速なネットワークで checkForUpdateAsync を実行する — Android の UI が数分間フリーズすることがあります。初回レンダリング後に、手動でタイムアウトを設定して実行してください。

  17. FlashList で expo-image の recyclingKey を設定しない — 設定しないと、再利用されたセルに前のアイテムの画像が一瞬表示されます。

  18. expo-secure-store の生体認証による無効化 — requireAuthentication を指定したキーは、生体認証情報が変更されると永久に読み取れなくなります。アプリデータの消去以外に復旧方法はありません。


リファレンスファイル

ファイル読むタイミング
references/react-query-rn.mdフォーカス/オンラインマネージャー、キャッシュの永続化、先読み
references/expo-router.md型付きルート、レイアウト、モーダル、認証、検索パラメーター
references/lists.mdFlashList と React Query、無限スクロール、パフォーマンス
references/zustand-rn.mdMMKV 永続化アダプター、暗号化、ハイドレーションのパターン
references/testing-rn.mdRNTL、Query/Router/Form/XState のテスト、RN での MSW
references/animations.mdReanimated、Gesture Handler のパターンと注意点
references/bottom-sheet.mdreact-native-true-sheet のセットアップ、デテント、Expo Router
references/notifications.mdexpo-notifications の権限、リスナー、バックグラウンド処理
references/expo-essentials.mdexpo-image、expo-secure-store、expo-haptics、expo-updates

コメント