LabHub

ブログ

状態管理ルネサンス 2025 — Zustand·Jotai·Valtio·TanStack Query·Signals·XState·RSC (シーズン6 第10回)

한국어English日本語

プロローグ — 状態管理ルネサンス

「どの状態管理ライブラリ?」は2018年の面接質問だった。2021年には皆がReduxボイラープレートを嫌い、2024年にはReduxToolkitをデフォルトにする人もいなくなった — 質問はどの状態をどこに?に分裂した。

今日、状態は明確に分かれた家に住む:

本稿は各々をマップ、どれに手を伸ばすか、よくあるアンチパターンを説明する。


1章. 状態の4カテゴリ

  1. サーバ状態 — バックエンドが所有。ユーザー情報、投稿、商品。キャッシュであり状態でない。
  2. URL状態 — URLがすでにエンコード。現在のタブ、ソート順、検索クエリ。
  3. UI状態 — 純粋にクライアント側のプレゼンテーション。メニュー開閉、モーダル、テーマ。
  4. フォーム状態 — 送信前の一時編集状態。

バグのほとんどは状態を間違ったカテゴリに置くことから来る。Reduxのサーバデータ = 古いバグ。ローカル状態のURL状態 = 壊れた戻るボタン。


2章. サーバ状態 — TanStack Queryが勝利

TanStack Query (旧 React Query) がサーバ状態カテゴリを食った。理由:

最小:

const { data, isLoading } = useQuery({
  queryKey: ['posts', postId],
  queryFn: () => fetch(`/api/posts/${postId}`).then(r => r.json()),
})

競合

ルール: サーバデータを決して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。キャンバス/ホワイトボードアプリに良。 用途: 多くのミューテーションポイントを持つ複雑なネスト状態。

選び方


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が重要か:

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' } },
  },
})

用途

使わない時

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推論良好。

どちらに手を伸ばすか


7章. URL状態 — useSearchParams, nuqs

ルール: ビューを変え、リンクで共有可能ならURL状態にすべし。タブ、フィルタ、ソート順、ページネーション。


8章. React Context — 誤解されたツール

Contextは依存注入機構であり、状態ライブラリでない。使う時:

使わない時:


9章. React Server Components — 状態でない状態

RSCは巨大クラスの問題のクライアント状態を排除。データがサーバから来てクライアントで変わらないなら、状態管理を必要としない。

ルール: ストアに追加する前に「これはRSCにできるか?」と問う。最高の状態は状態なし。


10章. 永続化 — localStorage, IndexedDB, cookies

Zustandにpersistミドルウェア、JotaiにatomWithStorage、ValtioにproxyWithHistory


11章. デバッグとDevTools

ルール: 状態解にタイムトラベルや状態ツリービューがないなら、後で代償を払う。


12章. 2026年のスタック

新Reactアプリの実用的デフォルト:


13章. 2026年以降の注視点

  1. React Compiler安定 — 自動メモ化が「性能」仕事を変える。useMemo/useCallbackが稀に。
  2. Signalsがweb標準 — TC39提案進行でブラウザネイティブプリミティブ。
  3. フレームワーク横断RSC — SolidStart、Remix (React Routerとの合併後)、AstroがRSC風境界を採用。
  4. アクターモデル標準化 — XState v5アクターパターンがメインストリームへ。
  5. Syncエンジン — Replicache、Zero (Replicacheチーム)、Liveblocksがサーバとクライアント状態の境界を曖昧に。「Local-first」上昇。

12項目チェックリスト

  1. サーバ、URL、クライアントUI、フォーム状態を分離?
  2. サーバデータはTanStack Query (またはSWR/Apollo) で、Reduxでない?
  3. URL状態が実際のURLに反映?
  4. ContextはDI用、頻繁変更状態でない?
  5. デフォルトクライアントストアが1つ (Zustandまたは同等)?
  6. フォームはRHF/TanStack Form + Zod使用?
  7. 複雑フローをXStateでモデル化?
  8. サーバフェッチUIにRSC使用?
  9. 各状態層にDevTools統合?
  10. 永続化が正しいストレージ (local vs IDB vs cookie)?
  11. useMemo/useCallback肥大を回避 (またはReact Compiler使用)?
  12. オンボード用に状態形状が文書化?

10アンチパターン

  1. Redux/Zustandにサーバデータ — 古いバグ。
  2. ローカル状態にURL状態 — 戻るボタン壊れ、共有不能リンク。
  3. 頻繁変更状態にContext — 再レンダー嵐。
  4. フォームでuseStateカスケード — 代わりにRHF。
  5. ローカルモーダル状態にグローバルストア。
  6. booleanトグルに状態機械。
  7. 複数Zustandストア重複。
  8. useEffectでフェッチ — TanStack Queryを使う。
  9. 「念のため」すべてをlocalStorageに永続化。
  10. 「我々は常にクライアント状態を使った」でRSC回避。

次回予告

シーズン6 第11回: フロントエンドテスト 2025 — Vitest、Jest、Bun test、Testing Library、Playwright、Storybook、MSW、ビジュアル回帰、AI生成テスト。何をどの比率で書き、CIグリーンを保つ方法。

— 状態管理ルネサンス編、完。

コメント

まだコメントはありません。

ログインするとコメントできます