✍️ 필사 모드: 状態管理ルネサンス 2025 — Zustand·Jotai·Valtio·TanStack Query·Signals·XState·RSC (シーズン6 第10回)
日本語プロローグ — 状態管理ルネサンス
「どの状態管理ライブラリ?」は2018年の面接質問だった。2021年には皆がReduxボイラープレートを嫌い、2024年にはReduxToolkitをデフォルトにする人もいなくなった — 質問は**どの状態をどこに?**に分裂した。
今日、状態は明確に分かれた家に住む:
- サーバ状態 → TanStack Query / SWR / Apollo。
- URL状態 → ルータパラメータ、検索パラメータ。
- Server Components → 状態がクライアントに届かない。
- クライアントUI状態 → Zustand / Jotai / Valtio / React Context。
- フォーム状態 → React Hook Form / TanStack Form。
- 状態機械 → XState / Robot / tiny-fsm。
- 派生/リアクティブ → Signals (Preact、Solid、Angular、React提案)。
本稿は各々をマップ、どれに手を伸ばすか、よくあるアンチパターンを説明する。
1章. 状態の4カテゴリ
- サーバ状態 — バックエンドが所有。ユーザー情報、投稿、商品。キャッシュであり状態でない。
- URL状態 — URLがすでにエンコード。現在のタブ、ソート順、検索クエリ。
- UI状態 — 純粋にクライアント側のプレゼンテーション。メニュー開閉、モーダル、テーマ。
- フォーム状態 — 送信前の一時編集状態。
バグのほとんどは状態を間違ったカテゴリに置くことから来る。Reduxのサーバデータ = 古いバグ。ローカル状態のURL状態 = 壊れた戻るボタン。
2章. サーバ状態 — TanStack Queryが勝利
TanStack Query (旧 React Query) がサーバ状態カテゴリを食った。理由:
- キャッシュ、重複排除、バックグラウンド再取得、stale-while-revalidate — 無料。
- ミューテーション + 楽観的更新。
- 無限クエリ、ページネーションヘルパ。
- fetch、axios、GraphQL、tRPC、Promiseを返すものなら何でも動作。
最小:
const { data, isLoading } = useQuery({
queryKey: ['posts', postId],
queryFn: () => fetch(`/api/posts/${postId}`).then(r => r.json()),
})
競合
- SWR — 軽め、類似アイデア。シンプルアプリに良。
- Apollo Client — GraphQLを使っているなら。
- RTK Query — Redux Toolkitの一部、既にそこにいるチーム向け。
ルール: サーバデータを決してRedux/Zustandに入れない。TanStack Queryを使う。
3章. クライアントUI状態 — Zustand, Jotai, Valtio
Zustand
グローバルストア、最小API、ボイラープレートなし。
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
強み: 小 (~1kb)、どこでも動作、Redux DevTools統合、immerミドルウェア。 用途: グローバルUI状態 (テーマ、モーダル、サイドバー)。シンプルアプリ。
Jotai
アトムベース。各状態がアトム、コンポーネントがアトムに購読。
import { atom, useAtom } from 'jotai'
const countAtom = atom(0)
const [count, setCount] = useAtom(countAtom)
強み: 細粒度リアクティブ、複雑な派生状態に良、ミドルウェア豊富。 用途: 小さな独立ピース多数、派生アトム、永続化/同期ニーズ。
Valtio
プロキシベース。状態をmutateすると再レンダー。
import { proxy, useSnapshot } from 'valtio'
const state = proxy({ count: 0 })
state.count++ // 購読コンポーネントで再レンダートリガ
強み: 命令的ミューテーションに最も自然なAPI。キャンバス/ホワイトボードアプリに良。 用途: 多くのミューテーションポイントを持つ複雑なネスト状態。
選び方
- 90%のアプリ → Zustand。
- 複雑な派生状態 → Jotai。
- 命令的ミューテーション重 (エディタ、ホワイトボード) → Valtio。
- エンタープライズ一貫性 → Redux Toolkit (今も最も安全な「チームオンボード」選択)。
4章. Signals — リアクティブ革命
SignalsはSolidが普及させた細粒度リアクティブプリミティブ。Preact、Svelte 5 (runes)、Angular、Vue (refs) が採用、TC39標準トラック提案。
// Preact Signals
import { signal, computed } from '@preact/signals'
const count = signal(0)
const doubled = computed(() => count.value * 2)
// 更新: count.value++
なぜSignalsが重要か:
- 再レンダーカスケードなし — signalを読むDOMノードのみ更新。
- 依存配列なし — 依存グラフが暗黙。
- 合成可能 —
computedがReactのuseMemo体操なしで派生状態を与える。
Reactの状況 (2026): TC39 Signals提案はStage 1。React自体はsignalsを採用せず、Reactチームはコンパイラベース自動メモ化 (React Compiler、2025 RC) を好む。今のところReactのsignalsはライブラリ経由 (@preact/signals-react、@preact/signals-react-runtime、またはsignal風アトムとしてJotai)。
5章. 状態機械 — XState
XStateは明示的な状態、遷移、ガードを持つアクターとして状態をモデル化。
import { createMachine } from 'xstate'
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
states: {
idle: { on: { FETCH: 'loading' } },
loading: { on: { SUCCESS: 'done', ERROR: 'error' } },
done: {},
error: { on: { RETRY: 'loading' } },
},
})
用途
- 複雑なUIフロー (複数ステップフォーム、ウィザード、オンボーディング)。
- 多くの状態を持つステータス (「isLoading / error / data」だけでない)。
- プロダクトマネージャ用の視覚状態図が必要。
使わない時
- シンプルなon/offフラグ。
- 完全にTanStack Queryが管理する状態。
XState v5 (2024) はTypeScriptエルゴノミクス、アクターモデル、小バンドルに注力。RobotとXState/fsmはより小さな代替。
6章. フォーム状態 — React Hook Form / TanStack Form
React Hook Form (RHF)
2020–2024のデフォルト。非制御入力、resolvers経由Zod統合、性能良好。
TanStack Form (2024+)
フレームワーク非依存、ヘッドレス。複雑フォームに豊か、TS推論良好。
どちらに手を伸ばすか
- 単一入力より長いフォームなら何でも。
useStateからフォーム状態を作らない。 - スキーマ + 検証にZodまたはValibot。
7章. URL状態 — useSearchParams, nuqs
- Next.js
useSearchParams+ Server Component読み取り。 nuqs— 型付き検索パラメータ、変更時URLに同期。@tanstack/router— 第一級検索パラメータを持つ型付きルータ。
ルール: ビューを変え、リンクで共有可能ならURL状態にすべし。タブ、フィルタ、ソート順、ページネーション。
8章. React Context — 誤解されたツール
Contextは依存注入機構であり、状態ライブラリでない。使う時:
- 変更頻度の低い依存 (テーマ、認証ユーザー、i18n)。
- propsドリルなしで物を下に渡す。
使わない時:
- 頻繁に変わる状態 (全コンシューマ再レンダー)。
- Zustand/Jotaiの置換として。
9章. React Server Components — 状態でない状態
RSCは巨大クラスの問題のクライアント状態を排除。データがサーバから来てクライアントで変わらないなら、状態管理を必要としない。
ルール: ストアに追加する前に「これはRSCにできるか?」と問う。最高の状態は状態なし。
10章. 永続化 — localStorage, IndexedDB, cookies
- localStorage — 同期、小、テーマ/設定用。
- IndexedDB (Dexieまたは
idb-keyval経由) — 大、非同期、オフラインデータ用。 - Cookies — サーバ可読状態 (認証、SSRが必要な設定)。
Zustandにpersistミドルウェア、JotaiにatomWithStorage、ValtioにproxyWithHistory。
11章. デバッグとDevTools
- Redux DevToolsがZustand、Jotai、Valtioと統合 — タイムトラベル、アクションログ。
- TanStack Query DevTools — キャッシュインスペクタ、クエリごとステータス。
- XState Inspector / @xstate/inspect — ライブ状態機械ビューア。
- React DevTools Profiler — レンダー追跡。
ルール: 状態解にタイムトラベルや状態ツリービューがないなら、後で代償を払う。
12章. 2026年のスタック
新Reactアプリの実用的デフォルト:
- サーバ状態: TanStack Query。
- クライアントUI状態: Zustand (1つのストア)。
- フォーム: React Hook Form + Zod。
- URL状態: nuqs (またはルータ内蔵)。
- 状態機械: 複雑さが正当化する時のみXState。
- 派生リアクティブ: React Compiler (2025 RC) またはJotaiアトム。
- RSCでサーバ専用データ。
13章. 2026年以降の注視点
- React Compiler安定 — 自動メモ化が「性能」仕事を変える。
useMemo/useCallbackが稀に。 - Signalsがweb標準 — TC39提案進行でブラウザネイティブプリミティブ。
- フレームワーク横断RSC — SolidStart、Remix (React Routerとの合併後)、AstroがRSC風境界を採用。
- アクターモデル標準化 — XState v5アクターパターンがメインストリームへ。
- Syncエンジン — Replicache、Zero (Replicacheチーム)、Liveblocksがサーバとクライアント状態の境界を曖昧に。「Local-first」上昇。
12項目チェックリスト
- サーバ、URL、クライアントUI、フォーム状態を分離?
- サーバデータはTanStack Query (またはSWR/Apollo) で、Reduxでない?
- URL状態が実際のURLに反映?
- ContextはDI用、頻繁変更状態でない?
- デフォルトクライアントストアが1つ (Zustandまたは同等)?
- フォームはRHF/TanStack Form + Zod使用?
- 複雑フローをXStateでモデル化?
- サーバフェッチUIにRSC使用?
- 各状態層にDevTools統合?
- 永続化が正しいストレージ (local vs IDB vs cookie)?
useMemo/useCallback肥大を回避 (またはReact Compiler使用)?- オンボード用に状態形状が文書化?
10アンチパターン
- Redux/Zustandにサーバデータ — 古いバグ。
- ローカル状態にURL状態 — 戻るボタン壊れ、共有不能リンク。
- 頻繁変更状態にContext — 再レンダー嵐。
- フォームで
useStateカスケード — 代わりにRHF。 - ローカルモーダル状態にグローバルストア。
- booleanトグルに状態機械。
- 複数Zustandストア重複。
useEffectでフェッチ — TanStack Queryを使う。- 「念のため」すべてをlocalStorageに永続化。
- 「我々は常にクライアント状態を使った」でRSC回避。
次回予告
シーズン6 第11回: フロントエンドテスト 2025 — Vitest、Jest、Bun test、Testing Library、Playwright、Storybook、MSW、ビジュアル回帰、AI生成テスト。何をどの比率で書き、CIグリーンを保つ方法。
— 状態管理ルネサンス編、完。
현재 단락 (1/142)
「どの状態管理ライブラリ?」は2018年の面接質問だった。2021年には皆がReduxボイラープレートを嫌い、2024年にはReduxToolkitをデフォルトにする人もいなくなった — 質問は**ど...