关于 zustand-state-management
面向 React 和 TypeScript 应用的 Zustand 状态管理指导,聚焦共享客户端状态、类型化 Store、选择器性能、中间件、SSR 与测试。
主要功能
- 01状态归属决策指导
- 02小型 Store 与类型化切片
- 03选择器性能实践
- 04持久化与 DevTools 中间件
- 05SSR 安全的 Store 与测试
适用场景
- 01设计 React 组件间的共享状态
- 02通过精简选择器减少重复渲染
- 03测试 Store 操作与中间件
- 04处理 SSR 应用中的持久化状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill zustand-state-management安装后在 Claude Code 中输入/zustand-state-management确认已加载。
详细介绍
Zustand 状态管理
此技能介绍如何在 React 和 TypeScript 应用中设计和使用 Zustand store,包括状态归属决策、store 切片、选择器性能、中间件、SSR 和测试。
状态归属
- 将短暂的 UI 状态保留在最近的组件中,并使用
useState或useReducer管理——不要默认使用 Zustand。 - 使用 URL 状态保存可分享的筛选条件、分页、标签页和搜索参数。
- 仅在客户端状态确实需要由互不相关的组件共享时才使用 Zustand(例如身份验证会话视图状态、命令面板、购物车草稿、编辑器状态等)。
- 使用 TanStack Query、SWR、RTK Query 或项目现有的数据层管理服务端状态——除非有明确记录的离线或草稿编辑需求,否则不要将获取的服务端数据复制到 Zustand store 中。
- 使用函数式、声明式模式;避免使用类。使用
isLoading、hasError这类带辅助动词的描述性变量名。
Store 设计
- 将每个 store 建模为状态加命名操作;避免暴露供组件代码滥用的匿名 setter。
- 保持 store 小巧且专注于特定领域,而不是为整个应用创建一个全局 store。
- 将大型 store 拆分为有类型的切片,然后仅在组合后的 store 边界应用中间件。
- 将派生值保留为选择器或小型纯辅助函数,除非必须将其缓存到状态中。
- 默认存储可序列化数据;将 DOM 节点、promise、套接字和计时器置于 store 状态之外。
import { create } from 'zustand'
interface SidebarState {
isOpen: boolean
activePanelId: string | null
}
interface SidebarActions {
openPanel: (panelId: string) => void
close: () => void
toggle: () => void
}
type SidebarStore = SidebarState & SidebarActions
export const useSidebarStore = create<SidebarStore>()((set) => ({
isOpen: false,
activePanelId: null,
openPanel: (panelId) => set({ isOpen: true, activePanelId: panelId }),
close: () => set({ isOpen: false, activePanelId: null }),
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}))
组件用法与性能
- 订阅尽可能小的切片:
useStore((state) => state.value)。避免选择整个 store。 - 除非组件确实需要每个字段,否则不要在组件中调用不带选择器的 store hook。
- 当选择器返回包含多个值的对象或元组时,使用
zustand/react/shallow中的useShallow,以避免每次调用产生新的对象引用而触发重新渲染。 - 保持选择器纯净且开销小;将昂贵的派生计算移至带记忆化的辅助函数中。
import { useShallow } from 'zustand/react/shallow'
import { useSidebarStore } from '@/stores/sidebar-store'
export function SidebarToggle() {
const { isOpen, toggle } = useSidebarStore(
useShallow((state) => ({
isOpen: state.isOpen,
toggle: state.toggle,
})),
)
return (
<button type="button" aria-expanded={isOpen} onClick={toggle}>
Toggle sidebar
</button>
)
}
TypeScript
- 为共享 store 定义显式的状态和操作接口;对于非简单 store,将它们组合起来(
type Store = State & Actions),而不是使用一个扁平接口。 - 避免使用
any;处理外部数据时使用unknown并进行类型收窄。 - 为操作的参数和返回值声明类型,包括异步操作。
- 对于复杂的本地状态,优先使用判别联合类型,而不是多个关联松散的布尔值。
- 当测试、实用工具或 vanilla store 工厂需要时,导出 store 状态类型。
中间件
更新
- 当下一个值取决于当前状态时,使用函数式
set((state) => nextState)。 - 以不可变方式处理嵌套状态;仅当
immer中间件能显著简化嵌套更新时才安装并使用它——不使用该中间件时,不要直接修改嵌套对象。
持久化
import { persist } from 'zustand/middleware'
const useStore = create(
persist(
(set) => ({
// state and actions
}),
{ name: 'store-key' }
)
)
- 仅对必须在重新加载后保留的状态使用
persist。 - 持久化超出简单偏好设置的内容时,使用
partialize、version和migrate。 - 切勿将密钥、访问令牌、刷新令牌、原始个人身份信息或其他长期授权状态持久化到浏览器存储中。
DevTools
import { devtools } from 'zustand/middleware'
const useStore = create(
devtools((set) => ({
// state and actions
}))
)
- 在开发环境中,对复杂流程使用
devtools,并为重要操作设置清晰的名称,以便在跟踪记录中识别。
非 React 订阅
- 对于需要在组件渲染之外进行细粒度更新的非 React 订阅,使用
subscribeWithSelector。
异步操作与错误处理
- 异步 store 操作应协调仅限客户端的工作流、乐观草稿或本地设备 API;除非状态明确归客户端所有,否则应将 HTTP 获取放在项目的服务端状态层。
- 使用
idle、pending、success和error等明确状态表示异步客户端工作流,而不是使用临时布尔值。 - 在函数开头通过提前返回和守卫子句处理错误;在异步操作中使用 try-catch,并提供有意义的错误消息。
- 重试或关闭工作流时,有意重置错误状态。
SSR 和 React Server Components
- 不要在 React Server Components 中读取或修改仅限浏览器的 store。
- 在 SSR 框架(Next.js App Router 等)中,如果必须在服务器上初始化状态,应为每个请求创建 vanilla store,而不是使用模块级共享 store。
- 在渲染由存储支持的值之前,防止持久化 store 导致 hydration 不匹配。
- 除中间件配置外,store 模块中不要直接访问
window、document和存储。
测试
- 独立于组件测试 store;尽可能直接测试 store 操作,而不渲染 React。
- 使用初始状态在各项测试之间重置 store。
- 将选择器和操作的断言与组件行为分开;隔离 UI 逻辑时,在组件测试中模拟 Zustand store。
- 模拟服务端状态库,而不是为了测试将获取的数据经由 Zustand 传递。
- 将中间件行为(持久化、devtools)与核心 store 逻辑分开测试。
反模式
- 不要为整个应用创建一个全局 store。
- 除非多个相距较远的组件需要编辑同一份草稿,否则不要将表单输入状态放入 Zustand。
- 不要在未使用 Immer 中间件的情况下直接修改嵌套对象。
- 不要将 Zustand 用作事件总线;优先使用显式回调、服务或作用域限定的 store。
- 除非项目已经采用这种模式,否则不要引入 Redux 风格的 reducer、action 常量或 dispatch 封装。
评论
支持 Markdown