mcpField
Z
zustand-state-management

zustand-state-management

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

zustand-state-management について

React と TypeScript アプリで Zustand の状態ストアを構築するための実用スキルです。型付きストアや永続化、セレクター、ミドルウェア、一般的なパターンとトラブルシューティングを扱います。

主な機能

  1. 01型付きストアの設定
  2. 02セレクターによる状態アクセス
  3. 03localStorage または sessionStorage での永続化
  4. 04devtools などのミドルウェア
  5. 05パターンとトラブルシューティングの資料

活用シーン

  1. 01React アプリへのクライアント状態の追加
  2. 02再読み込み後もユーザー設定を保持
  3. 03TypeScript のミドルウェアやハイドレーションの問題調査

インストール

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

$npx skills add https://github.com/secondsky/claude-skills --skill zustand-state-management

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

同梱ファイル

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

assets/
example-template.txt
references/
advanced-topics.md
common-patterns.md
example-reference.md
known-issues.md
middleware-guide.md
migration-guide.md
nextjs-hydration.md
typescript-patterns.md
scripts/
check-versions.sh
example-script.sh
templates/
async-actions-store.ts
basic-store.ts
computed-store.ts
devtools-store.ts
nextjs-store.ts
persist-store.ts
slices-pattern.ts
typescript-store.ts

詳細

Zustand 状態管理

ステータス:本番環境で使用可能 ✅ 最終更新日:2026-08-03 最新バージョン:[email protected] 依存関係:React 18+、TypeScript 5+


クイックスタート(3 分)

1. Zustand をインストールする
bun add zustand  # preferred
# or: npm install zustand
# or: yarn add zustand

Zustand を選ぶ理由

  • 最小限の API:覚える関数は 1 つだけです(create)
  • ボイラープレート不要:プロバイダー、リデューサー、アクションは不要です
  • TypeScript を重視:優れた型推論が利用できます
  • 高速:粒度の細かいサブスクリプションにより、不要な再レンダリングを防ぎます
  • 柔軟:永続化、開発者ツールなどのミドルウェアを利用できます
2. 最初のストアを作成する(TypeScript)
import { create } from 'zustand'

interface BearStore {
  bears: number
  increase: (by: number) => void
  reset: () => void
}

const useBearStore = create<BearStore>()((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
  reset: () => set({ bears: 0 }),
}))

重要:create<T>()() の二重の括弧に注目してください。ミドルウェアを使用する TypeScript では、これが必須です。

3. コンポーネントでストアを使う
import { useBearStore } from './store'

function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

function Controls() {
  const increase = useBearStore((state) => state.increase)
  return <button onClick={() => increase(1)}>Add bear</button>
}

これが機能する理由

  • 選択した状態が変化したときだけ、コンポーネントが再レンダリングされます
  • Context プロバイダーは不要です
  • セレクター関数で状態の特定の部分を取り出します

3 つのパターンによるセットアップ手順

パターン 1:基本的なストア(JavaScript)

TypeScript を使わないシンプルな用途向けです。

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

使用する場面

  • プロトタイピング
  • 小規模なアプリ
  • プロジェクトで TypeScript を使わない場合
パターン 2:TypeScript のストア(推奨)

型安全性が必要な本番アプリ向けです。

import { create } from 'zustand'

// Define store interface
interface CounterStore {
  count: number
  increment: () => void
  decrement: () => void
}

// Create typed store
const useCounterStore = create<CounterStore>()((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

要点

  • 状態とアクションのインターフェースを分けます
  • create<T>()() 構文を使います(ミドルウェアのためのカリー化)
  • IDE の完全な自動補完と型チェックが利用できます
パターン 3:永続化するストア

ページを再読み込みしても状態を保持する場合に使います。

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface UserPreferences {
  theme: 'light' | 'dark' | 'system'
  language: string
  setTheme: (theme: UserPreferences['theme']) => void
  setLanguage: (language: string) => void
}

const usePreferencesStore = create<UserPreferences>()(
  persist(
    (set) => ({
      theme: 'system',
      language: 'en',
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
    }),
    {
      name: 'user-preferences', // unique name in localStorage
      storage: createJSONStorage(() => localStorage), // optional: defaults to localStorage
    },
  ),
)

重要な理由

  • 状態が自動的に localStorage に保存されます
  • ページの再読み込み時に復元されます
  • sessionStorage にも対応しています
  • シリアライズは自動的に処理されます

重要なルール

必ず行うこと

✅ TypeScript でミドルウェアとの互換性を確保するため、create<T>()()(二重の括弧)を使います
✅ 状態とアクションのインターフェースを分けて定義します
✅ セレクター関数を使って、状態の特定の部分を取り出します
✅ 派生状態には更新関数を使って set を呼び出します:set((state) => ({ count: state.count + 1 }))
✅ persist ミドルウェアのストレージキーには、一意の名前を使います
✅ hasHydrated フラグパターンを使って、Next.js のハイドレーションを処理します
✅ 複数の値を選択する場合は shallow を使います
✅ アクションは純粋に保ちます(状態の更新以外の副作用を起こさないようにします)

絶対に行わないこと

❌ TypeScript で create<T>(...)(括弧が 1 組)を使わないでください。ミドルウェアの型が壊れます
❌ 状態を直接変更しないでください:set((state) => { state.count++; return state })。不変な更新を使います
❌ セレクター内で新しいオブジェクトを作成しないでください:useStore((state) => ({ a: state.a }))。無限に再レンダリングされます
❌ 複数のストアで同じストレージ名を使わないでください。データが衝突します
❌ ハイドレーションの確認なしに SSR 中に localStorage にアクセスしないでください
❌ サーバー状態に Zustand を使わないでください。代わりに TanStack Query を使います
❌ ストアのインスタンスを直接エクスポートしないでください。必ずフックをエクスポートします


既知の問題の予防(5 件)

問題エラー簡単な解決方法
#1 ハイドレーションの不一致"Text content does not match"_hasHydrated フラグと onRehydrateStorage を使います
#2 TypeScript の型推論ミドルウェア使用時に型が壊れるcreate<T>()() の二重の括弧を使います
#3 インポートエラー"createJSONStorage not exported"[email protected]+ にアップグレードします
#4 無限ループブラウザーがフリーズするshallow または個別のセレクターを使います
#5 スライスの型StateCreator の型が機能しないStateCreator<Combined, [], [], Slice> を明示的に指定します

最も重要な点 - TypeScript では二重の括弧を使います:

// ❌ WRONG: create<T>((set) => ...)
// ✅ CORRECT: create<T>()((set) => ...)

参照:コード例を含む解決方法の全容は references/known-issues.md をご覧ください。


ミドルウェアの設定

import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

const useStore = create<MyStore>()(
  devtools(
    persist(
      (set) => ({ /* store definition */ }),
      { name: 'my-storage' },
    ),
    { name: 'MyStore' },
  ),
)
ミドルウェア目的インポート
persistlocalStorage/sessionStoragezustand/middleware
devtoolsRedux DevTools との統合zustand/middleware
immerミュータブルな更新構文zustand/middleware/immer

順序が重要です:devtools(persist(...)) とすると、persist のアクションが DevTools に表示されます。

参照:ミドルウェアの詳細については references/middleware-guide.md をご覧ください。


よく使われるパターン

パターン用途主なテクニック
計算値派生データセレクター内で計算:state.items.length
非同期アクションAPI 呼び出しset({ isLoading: true }) + try/catch
ストアのリセットログアウト、フォームのクリアset(initialState)
パラメーター付きセレクター動的なアクセスstate.todos.find(t => t.id === id)
複数のストア関心事の分離別々の create() 呼び出しを作成

参照先:完全な実装については references/common-patterns.md をご覧ください。


高度なトピック

トピック用途主な API
バニラストアReact を使わない環境、テストzustand/vanilla の createStore()
カスタムミドルウェアログ記録、タイムスタンプStateCreator をラップ
Immer可変形式の更新構文immer() ミドルウェア
サブスクリプション副作用store.subscribe()

参照先:完全な実装については references/advanced-topics.md をご覧ください。


同梱リソース

種類ファイル
テンプレートbasic-store.ts、typescript-store.ts、persist-store.ts、slices-pattern.ts、devtools-store.ts、nextjs-store.ts、computed-store.ts、async-actions-store.ts
リファレンスmiddleware-guide.md、typescript-patterns.md、nextjs-hydration.md、migration-guide.md、known-issues.md、common-patterns.md、advanced-topics.md

リファレンスを読み込むタイミング

リファレンス読み込むタイミング
known-issues.mdハイドレーション、TypeScript、無限ループ、またはスライスに関するエラーをデバッグするとき
common-patterns.md計算値、非同期アクション、リセットパターンを実装するとき
advanced-topics.mdバニラストア、カスタムミドルウェア、Immer、サブスクリプションを扱うとき
middleware-guide.mdpersist、devtools の設定や、ミドルウェアの組み合わせを行うとき
typescript-patterns.md複雑な型推論の問題や、StateCreator に関する問題があるとき
nextjs-hydration.mdNext.js の SSR/ハイドレーションに関する問題があるとき
migration-guide.mdRedux、Context API、または Zustand v4 から移行するとき

トラブルシューティングの早見表

問題解決策
ストアを更新しても再レンダリングが発生しない分割代入ではなく、セレクターを使用します:useStore(state => state.value)
ミドルウェアで TypeScript エラーが発生するcreate<T>()() の二重括弧を使用します
persist でハイドレーションエラーが発生する_hasHydrated フラグパターンを実装します
DevTools にアクションが表示されないアクション名を渡します:set(newState, undefined, 'actionName')
ストアが予期せずリセットされる開発中は HMR によってリセットが発生します

依存関係

{ "dependencies": { "zustand": "^5.0.14", "react": "^18.0.0+" } }

互換性:React 18 以降、React 19、TypeScript 5 以降、Next.js 14 以降、Vite 5 以降


公式ドキュメント:https://zustand.docs.pmnd.rs/ | GitHub:https://github.com/pmndrs/zustand

コメント