关于 zustand-state-management
为 React 应用提供 Zustand v5 使用规范的开发技能,重点涵盖小型 Store、自定义 Hook、选择器模式,以及区分客户端 UI 状态与服务端和 URL 状态。
主要功能
- 01Zustand v5 Store 规范
- 02自定义 Hook 导出模式
- 03原子选择器指导
- 04事件式 Action 设计
- 05服务端与 URL 状态边界
适用场景
- 01在组件间共享 UI 状态
- 02保存临时客户端偏好
- 03通过自定义 Hook 结合 TanStack Query
- 04按功能组织 Store
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fixmyberlin/ fixmyskills --skill zustand-state-management安装后在 Claude Code 中输入/zustand-state-management确认已加载。
详细介绍
**大模型参考:**获取 llms.txt,查看文档索引和最新 API。完整文档:llms-full.txt。面向读者的文档:zustand.docs.pmnd.rs。
核心思路:使用 Zustand(Dominik Dorfmeister)——仅导出自定义钩子,使用原子选择器、actions 命名空间、事件式操作,拆分为多个小型 store,并通过自定义钩子与 Query/URL 结合。
**版本:**Zustand v5。在应用中固定使用 zustand@^5;升级时请查看 迁移到 v5。
何时使用 Zustand
| 使用 Zustand 的场景 | 使用其他方案的场景 |
|---|---|
| 在多个组件间共享的客户端 UI 状态(面板、地图工具、向导步骤) | 服务端数据 → TanStack Query |
| 不在 URL 中的临时客户端偏好设置 | 可共享的 URL 状态 → 路由器搜索参数(不使用 Router 的 Vite 应用:nuqs);切勿在 Zustand 中重复存储 |
| 小型、同步的客户端状态 | 身份验证会话 → skill tanstack-start-auth |
大多数应用状态都属于服务端状态或 URL 状态。只有在需要尚未由其他方案处理的客户端全局状态时,才添加 Zustand store。
文件和导出命名
**一个 store 文件对应一个关注点。**与功能放在一起(Start 项目的目录结构请参见 tanstack-start-conventions;SPA 应用:放在 components/ / shared/ 下的功能文件夹中)。
| 项目 | 约定 | 示例 |
|---|---|---|
| 文件 | {domain}-store.ts(kebab-case,以 -store 结尾) | map-filters-store.ts |
| 文件夹 | 放在功能旁,例如 src/features/map/ | 除非内容共享,否则不要将所有内容堆在一个全局 stores/ 文件夹中 |
| 状态类型 | {Domain}Store(PascalCase 接口) | MapFiltersStore |
内部 create 钩子 | use{Domain}Store——绝不导出 | useMapFiltersStore |
| 导出的钩子 | 状态基本项使用 use{Slice};actions 使用 use{Domain}Actions | useAppliedFilters、useMapFiltersActions |
不要使用:store.ts、state.ts、zustand.ts 或 useMapFiltersStore.ts(文件名不得暗示会导出原始 store)。不要在一个文件中放入互不相关的领域。
**持久化:**每个 store 使用唯一的 name,并与文件名对应,例如,map-filters-store.ts 对应 fmc-map-filters。**TanStack Start SSR + persist:**遵循 skill tanstack-start-conventions 和 llms.txt → SSR and Hydration(水合门控或官方 SSR 安全模式)。
标准 store 结构
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) => …)(双括号),即使不使用中间件也一样。
规则
- 仅导出自定义 Hook(
use{Slice}+use{Domain}Actions);将use{Domain}Store保持为私有。调用 store Hook 时必须传入选择器。 - 使用原子选择器,返回基本类型或稳定引用;每个 slice 导出一个 Hook。只有在两个值很少需要同时使用时,才使用
useShallow(来自zustand/shallow),条件允许时优先使用原子 Hook。 - 将
actions与状态分开。actions引用是稳定的,因此使用const { addFilter } = useMapFiltersActions()没问题。 - **将操作建模为事件,而非设置器:**使用
addFilter、closePanel,而非在组件中调用setApplied。业务逻辑留在 store 中。对于高频输入(调整大小、观察器),如果没有变化,则从set返回之前的state。 - 优先使用多个小型 store,而非一个全应用级 store。通过自定义 Hook 在边界处组合(例如,读取
useCredentialsStore+useUsersStore的 Hook)。除非有充分理由,否则避免使用 slices 模式(TypeScript 复杂度较高;参见 llms.txt)。 - **通过自定义 Hook 与 TanStack Query 组合使用:**筛选条件放在 Zustand 中,服务端列表放在 Query 中(
queryKey: ['todos', filters])。 - 不要用 Zustand 存储从服务端获取的数据。
查阅 llms.txt
不要在此技能中重复完整的 API 文档。请获取 llms.txt,了解中间件(persist、devtools、immer;persist 从 zustand/middleware 导入)、模式(slices、重置状态、vanilla store、测试)、集成(SSR 和 Hydration、URL hash、Maps/Sets)以及 v4 → v5 迁移。
相关技能
| 主题 | 技能 |
|---|---|
| 应用布局、SSR(Start) | tanstack-start-conventions |
| URL 搜索(Router) | tanstack-router-conventions |
| URL 查询状态(无 Router) | nuqs |