zustand-state-management について
React アプリで Zustand v5 を使うための開発スキルです。小さなストア、カスタムフック、セレクターの設計に加え、クライアント UI 状態とサーバー・URL 状態の区別を扱います。
主な機能
- 01Zustand v5 のストア規約
- 02カスタムフックのエクスポート方針
- 03アトミックセレクターの指針
- 04イベント型アクションの設計
- 05サーバー・URL 状態との区分
活用シーン
- 01コンポーネント間の UI 状態共有
- 02一時的なクライアント設定の保存
- 03カスタムフックでの TanStack Query 連携
- 04機能単位のストア構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ fixmyberlin/ fixmyskills --skill zustand-state-managementインストール後、Claude Code で/zustand-state-managementを入力して読み込まれたか確認します。
詳細
LLM リファレンス: ドキュメントの目次と最新の API を確認するには、llms.txt を取得してください。全ドキュメント:llms-full.txt。人が読みやすいドキュメント:zustand.docs.pmnd.rs。
基本的な考え方: Zustand の使い方(Dominik Dorfmeister)— カスタムフックのみをエクスポートし、アトミックセレクター、actions 名前空間、イベント形式のアクション、多数の小さなストアを使用し、カスタムフックを介して Query/URL と組み合わせます。
バージョン: Zustand v5。アプリでは zustand@^5 に固定してください。アップグレード時は v5 への移行を確認してください。
Zustand を使う場面
| Zustand を使う対象 | 別のものを使う対象 |
|---|---|
| コンポーネント間で共有するクライアント UI の状態(パネル、地図ツール、ウィザードのステップ) | サーバーデータ → TanStack Query |
| URL に含めない一時的なクライアント設定 | 共有可能な状態/URL の状態 → ルーターの検索パラメーター(Router を使わない Vite アプリ:nuqs)。Zustand で重複して管理しないこと |
| 小規模で同期的なクライアント状態 | 認証セッション → スキル tanstack-start-auth |
アプリの状態の多くは、サーバーまたは URL の状態です。すでに別の方法で管理されている状態ではない、クライアント全体で使う状態が必要な場合にのみ、Zustand ストアを追加してください。
ファイル名とエクスポート名
ストアファイル 1 つにつき、関心事は 1 つです。 機能と同じ場所に配置します(Start のレイアウトについては tanstack-start-conventions を参照。SPA アプリでは components//shared/ の下に機能ごとのフォルダーを配置します)。
| 項目 | 規約 | 例 |
|---|---|---|
| ファイル | {domain}-store.ts(ケバブケース、末尾に -store) | map-filters-store.ts |
| フォルダー | 機能の隣。例:src/features/map/ | 共有されている場合を除き、全ストアを単一の stores/ にまとめない |
| 状態の型 | {Domain}Store(パスカルケースのインターフェース) | MapFiltersStore |
内部の create フック | use{Domain}Store — エクスポートしないこと | useMapFiltersStore |
| エクスポートするフック | 状態のプリミティブには use{Slice}、actions には use{Domain}Actions | useAppliedFilters、useMapFiltersActions |
使用しないもの: store.ts、state.ts、zustand.ts、useMapFiltersStore.ts(ファイル名から生のストアをエクスポートすると誤解されるため)。関係のないドメインを 1 つのファイルにまとめないでください。
永続化: ストアごとに一意の name を指定し、ファイル名に合わせます。例:map-filters-store.ts には fmc-map-filters を指定します。TanStack Start の SSR + persist: スキル tanstack-start-conventions と llms.txt → SSR and Hydration に従ってください(ハイドレーションゲートまたは公式の SSR セーフなパターン)。
標準的なストアの形
import { create } from 'zustand'
interface MapFiltersStore {
applied: string[]
actions: {
addFilter: (filter: string) => void
clearFilters: () => void
}
}
const useMapFiltersStore = create<MapFiltersStore>()((set) => ({
applied: [],
actions: {
addFilter: (filter) =>
set((state) => (state.applied.includes(filter) ? state : { applied: [...state.applied, filter] })),
clearFilters: () => set({ applied: [] }),
},
}))
export const useAppliedFilters = () => useMapFiltersStore((state) => state.applied)
export const useMapFiltersActions = () => useMapFiltersStore((state) => state.actions)
TypeScript: ミドルウェアを使わない場合も、常に create<StoreType>()((set) => …)(二重の括弧)を使用してください。
ルール
- カスタムフックのみをエクスポートします(
use{Slice}+use{Domain}Actions)。use{Domain}Storeは非公開にします。セレクターを指定せずにストアフックを呼び出さないでください。 - プリミティブ値または安定した参照を返すアトミックなセレクターを使い、スライスごとにエクスポートするフックは1つにします。2つの値を同時に必要とすることがまれな場合に限り、
useShallow(zustand/shallowから)を使い、可能な限りアトミックなフックを優先します。 actionsを状態から分離します。actionsの参照は安定しているため、const { addFilter } = useMapFiltersActions()のようにして問題ありません。- アクションはセッターではなくイベントとしてモデル化します。 コンポーネントから
setAppliedを呼び出すのではなく、addFilterやclosePanelのようにします。ビジネスロジックはストアに置きます。高頻度で発生する入力(リサイズ、オブザーバー)では、変更がない場合にsetから以前のstateを返します。 - アプリ全体で1つのストアを使うより、小さなストアを多数使います。 カスタムフックで境界部分をまとめます(例:
useCredentialsStoreとuseUsersStoreを読み取るフック)。強い理由(TypeScriptの負荷が大きい場合など)がない限り、スライスパターンは避けてください。詳しくはllms.txtを参照してください。 - カスタムフックを使ってTanStack Queryと組み合わせます。 フィルターはZustandに、サーバーのリストはQueryに置きます(
queryKey: ['todos', filters])。 - サーバーから取得したデータにZustandを使わないでください。
llms.txtに委ねる内容
このスキル内でAPIドキュメント全体を重複させないでください。ミドルウェア(persist、devtools、immer。persistはzustand/middlewareからインポートします)、パターン(スライス、状態のリセット、バニラストア、テスト)、連携機能(SSRとHydration、URLハッシュ、Maps/Sets)、v4からv5への移行については、llms.txtを取得してください。
関連スキル
| トピック | スキル |
|---|---|
| アプリのレイアウト、SSR(Start) | tanstack-start-conventions |
| URL検索(Router) | tanstack-router-conventions |
| URLクエリ状態(Routerなし) | nuqs |