mcpField
Z
zustand

zustand

github.com/lobehub開発83.6k6.9k 回インストール今日更新
リポジトリ

zustand について

LobeHub開発向けに、Zustandの状態管理規約をまとめたスキルです。状態のみのストア、replicaによるサーバーデータ管理、既存ストアの編集や移行方法を扱います。

主な機能

  1. 01状態のみのストア構成を定義
  2. 02公開アクションと非公開アクションを分離
  3. 03replicaでサーバーデータを管理
  4. 04ハイドレーションと楽観的更新に対応
  5. 05既存ストアの規約に準拠

活用シーン

  1. 01Zustandストアの作成や移行
  2. 02replicaベースの一覧・詳細データを追加
  3. 03ロールバック付きの楽観的書き込みを実装

インストール

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

$npx skills add https://github.com/lobehub/lobehub --skill zustand

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

同梱ファイル

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

references/
data-structures.md
legacy-slices.md
references/data-structures/
reducer.md
types.md

詳細

LobeHub Zustand 状態管理

状態の構造と型

  • 共有ストアの型は @lobechat/database ではなく、@lobechat/types からインポートしてください。
  • 軽量なリスト項目の型と詳細情報全体の型は分けてください。リストの型は、重い詳細情報全体の型を継承してはいけません。
  • キャッシュされた詳細情報は ID をキーにしてください(xxxDetailMap: Record<string, Detail>)。単一の currentDetail オブジェクトを保持してはいけません。
  • コンポーネント内で場当たり的なラムダ式を繰り返すのではなく、xxxSelectors の集約(selectors.ts)を通して読み取ってください。
  • リスト/詳細の構造や型の参照元を決める前に、データ構造を読んでください。

状態のみのストア(新規ストアおよび移行後のストアでデフォルト)

アクションは Zustand の状態に含めません。参照先:src/store/page。

ファイル所有するもの
store.tsuseXStore = createWithEqualityFn<XState>()(devtools(() => initialState), shallow):状態のみ。メソッドは含めません
privateAction.tsXPrivateAction:レプリカのスライス、エンティティのリンク、およびパブリックアクション間で共有する書き込みヘルパー。index.ts からエクスポートしません
action.tsXActionImpl(パブリック API)と、シングルトンの xActions = new XActionImpl(setState, getState, new XPrivateAction(…))
  • コンポーネントでは useXStore(selector) で状態を読み取り、xActions.foo() を直接呼び出してください。ストアからアクションを選択してはいけません。
  • 同期フックはパブリッククラスに置き、xActions.useFetchX(params) のように呼び出します。
  • パブリックアクション同士は this.foo() を通じて呼び出します。プライベートヘルパーには this.#private を通じてアクセスします。クラス間でロジックを共有するために、ストアに internal_* メソッドを追加しないでください。
  • すべての書き込みにラベルを付けてください:this.#set(partial, false, n('actionName'))。ここで const n = setNamespace('x') とします。
  • パブリッククラスに reset() を置き、src/store/utils/userDataStores.ts の resetableActions に xActions を登録してください。
  • テストでは xActions を操作し、useXStore.getState() をアサートします。コンポーネントテストでモックする場合:vi.mock('@/store/x', () => ({ xActions: { foo: vi.fn() }, useXStore: … }))。

レプリカを基盤としたサーバーデータ

UI に表示するサーバーの一覧と詳細は、手書きの SWR onSuccess + reducer ではなく、@/libs/replica を経由します。エンジンが IndexedDB からのハイドレーション、ヘッドの再検証、ページング、ロールバック付きの楽観的オーバーレイ、スコープ(ユーザー+ワークスペース)の分離を担います。API リファレンスは、local-replica README を参照してください。エンジンは lobehub/local-replica で保守されています。そこで修正して公開し、こちらで依存関係を更新してください。

  1. ストアの projection.ts でリソース(defineReplica/definePagedReplica)を定義し、境界でサーバーの行データを変換します。
  2. レプリカごとに状態フィールドを 2 つ追加します。ビュー(xxxMap: Record<key, TData>)と、管理情報(xxxReplica: createReplicaState())です。
  3. プライベートクラスでバインドします。createReplicaSlice(resource, { get, set, stateKey: 'xxxReplica', view: recordLens('xxxMap'), … }) を使います。同じエンティティが複数のレプリカ(一覧行+詳細)に存在する場合は、linkReplicaEntity([list, detail]) でリンクします。
  4. 読み込み:slice.useSync(params) が返すのは取得状態のフラグ(isHydrated、isValidating、error、revalidate)のみです。データは xxxMap を参照するセレクターから取得します。「エントリーが undefined」であることが読み込み中の合図です。xxxInit フラグや loadingXxxIds は追加しないでください。
  5. ビューをマウントせずに読み込む場合(ホバー時のプリフェッチ、ミューテーション後の更新):await slice.fetch(params) を使います(書き込み後は { fresh: true })。スコープ、削除、クリア、リクエストの置き換えに対するガードを回避してしまうため、サービスを直接呼び出して slice.replace(...) を手動で行わないでください。各レスポンスに伴う副作用(not-found マップ、別のストアへのシード処理)は、単一のフックの onSuccess ではなく、isMissing を使ってスライスの effects(onApplied/onMissing/onPurge/onError)に記述します。
  6. スケルトンなしでコールドスタートする場合:useSync によるハイドレーションはコンポーネントのマウント後にのみ行われます。ローカルコピーから描画する必要がある一覧では、preHydrate アクション(ensureScope + hydrate)を追加し、@/libs/replica/prePaint の settleWithin(…, PRE_PAINT_HYDRATE_TIMEOUT) で時間を制限しながら、遅延ラッパー(src/spa/router/pageListLoader.ts、agentChatTopicListLoader.ts)を介してルートの loader から呼び出します。@/libs/replica はキャッシュスコープ、IndexedDB/localStorage ストレージ(@/libs/replica/storage の AppIndexedDBStorage/AppLocalStorage)、アプリの SWR ドライバーを設定します。パッケージの README はアプリに依存しない内容のままにしてください。
  7. 書き込み:slice.optimistic(key, apply, serverCall) または entity.optimistic(id, fn | 'remove', serverCall) を使います。呼び出しに失敗すると、確定済みの値にロールバックされるため、削除も楽観的に処理されます。クライアントにのみ存在する行(一時 ID)は isClientOnly で宣言し、update(key, fn, { persist: false }) でシードします。

テスト用のシード処理を除き、レプリカのビューへの書き込みは必ずそのスライスを通してください。通常の set では、エンジンの管理情報(永続化、ロールバックの基点、エンティティのリンク)が迂回されます。

レガシーストア

アクションクラスを状態にフラット化するストア(flattenActions、create*Slice、internal_*、internal_dispatch* + reducers、SWR onSuccess による同期)は、レガシースライスに従います。この構成は、そうしたストアを編集するときにのみ使用してください。データをレプリカに移行する際は、上記の構成に移行してください。

コメント