名称:zustand-state-management 描述:| 用于 React 应用的 Zustand 状态管理生产测试设置。 此技能提供了构建可扩展、类型安全的全局状态的全面模式。
使用场景:在 React 中设置全局状态,从 Redux 或 Context API 迁移,使用 localStorage 实现状态持久化,配置 TypeScript 与 Zustand,使用切片模式实现模块化存储,添加 devtools 中间件进行调试,处理 Next.js SSR 水合,或遇到水合错误、TypeScript 推断问题或持久化中间件问题。
预防 5 个文档化问题:Next.js 水合不匹配、TypeScript 双括号语法错误、持久化中间件导出错误、无限渲染循环和切片模式类型推断失败。
关键词:zustand,状态管理,React 状态,TypeScript 状态,持久化中间件,devtools,切片模式,全局状态,React hooks,create store,useBoundStore,StateCreator,水合错误,文本内容不匹配,无限渲染,localStorage,sessionStorage,immer 中间件,浅相等,选择器模式,zustand v5 许可证:MIT
Zustand 状态管理
状态:生产就绪 ✅ 最后更新:2025-10-24 最新版本:zustand@5.0.8 依赖项:React 18+,TypeScript 5+
快速开始(3分钟)
1. 安装 Zustand
npm install zustand
# 或
pnpm add zustand
# 或
yarn add zustand
为什么选择 Zustand?
- 最小 API:只需学习 1 个函数(
create) - 无模板代码:无需提供者、reducer 或动作
- TypeScript 优先:优秀的类型推断
- 快速:细粒度订阅防止不必要的重渲染
- 灵活:用于持久化、devtools 等的中间件
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'
// 定义存储接口
interface CounterStore {
count: number
increment: () => void
decrement: () => void
}
// 创建类型化存储
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', // localStorage 中的唯一名称
storage: createJSONStorage(() => localStorage), // 可选:默认为 localStorage
},
),
)
重要性:
- 状态自动保存到 localStorage
- 在页面重载时恢复
- 也适用于 sessionStorage
- 自动处理序列化
关键规则
始终执行
✅ 在 TypeScript 中使用 create<T>()()(双括号)以兼容中间件
✅ 为状态和动作定义独立接口
✅ 使用选择器函数提取特定状态切片
✅ 使用 set 与更新器函数用于派生状态:set((state) => ({ count: state.count + 1 }))
✅ 为持久化中间件存储键使用唯一名称
✅ 使用 hasHydrated 标志模式处理 Next.js 水合
✅ 使用 shallow 选择多个值
✅ 保持动作纯正(除了状态更新外无副作用)
永不执行
❌ 在 TypeScript 中使用 create<T>(...)(单括号) - 破坏中间件类型
❌ 直接突变状态:set((state) => { state.count++; return state }) - 使用不可变更新
❌ 在选择器中创建新对象:useStore((state) => ({ a: state.a })) - 导致无限渲染
❌ 为多个存储使用相同存储名称 - 导致数据冲突
❌ 在 SSR 中未检查水合情况下访问 localStorage
❌ 使用 Zustand 处理服务器状态 - 使用 TanStack Query 替代
❌ 直接导出存储实例 - 始终导出钩子
已知问题预防
此技能预防 5 个文档化问题:
问题 #1:Next.js 水合不匹配
错误:"文本内容与服务器渲染的 HTML 不匹配" 或 "水合失败"
来源:
- DEV Community: Next.js 中的持久化中间件
- GitHub 讨论 #2839
原因:持久化中间件在客户端读取 localStorage,但在服务器端不读取,导致状态不匹配。
预防:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface StoreWithHydration {
count: number
_hasHydrated: boolean
setHasHydrated: (hydrated: boolean) => void
increase: () => void
}
const useStore = create<StoreWithHydration>()(
persist(
(set) => ({
count: 0,
_hasHydrated: false,
setHasHydrated: (hydrated) => set({ _hasHydrated: hydrated }),
increase: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'my-store',
onRehydrateStorage: () => (state) => {
state?.setHasHydrated(true)
},
},
),
)
// 在组件中
function MyComponent() {
const hasHydrated = useStore((state) => state._hasHydrated)
if (!hasHydrated) {
return <div>Loading...</div>
}
// 现在可以安全渲染持久化状态
return <ActualContent />
}
问题 #2:TypeScript 双括号缺失
错误:类型推断失败,StateCreator 类型与中间件中断
原因:柯里化语法 create<T>()() 是中间件与 TypeScript 推断工作所必需的。
预防:
// ❌ 错误 - 单括号
const useStore = create<MyStore>((set) => ({
// ...
}))
// ✅ 正确 - 双括号
const useStore = create<MyStore>()((set) => ({
// ...
}))
规则:始终在 TypeScript 中使用 create<T>()(),即使没有中间件(为未来兼容)。
问题 #3:持久化中间件导入错误
错误:"尝试导入错误:'createJSONStorage' 未从 'zustand/middleware' 导出"
来源:GitHub 讨论 #2839
原因:错误的导入路径或 zustand 与构建工具之间的版本不匹配。
预防:
// ✅ v5 的正确导入
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
// 验证版本
// zustand@5.0.8 包括 createJSONStorage
// zustand@4.x 使用不同 API
// 检查您的 package.json
// "zustand": "^5.0.8"
问题 #4:无限渲染循环
错误:组件无限重渲染,浏览器冻结
来源:GitHub 讨论 #2642
原因:在选择器中创建新对象引用导致 Zustand 认为状态已更改。
预防:
import { shallow } from 'zustand/shallow'
// ❌ 错误 - 每次创建新对象
const { bears, fishes } = useStore((state) => ({
bears: state.bears,
fishes: state.fishes,
}))
// ✅ 正确选项 1 - 分别选择原始值
const bears = useStore((state) => state.bears)
const fishes = useStore((state) => state.fishes)
// ✅ 正确选项 2 - 使用 shallow 选择多个值
const { bears, fishes } = useStore(
(state) => ({ bears: state.bears, fishes: state.fishes }),
shallow,
)
问题 #5:切片模式 TypeScript 复杂性
错误:StateCreator 类型推断失败,复杂中间件类型中断
来源:官方切片模式指南
原因:组合多个切片需要显式类型注解以兼容中间件。
预防:
import { create, StateCreator } from 'zustand'
// 定义切片类型
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fishes: number
addFish: () => void
}
// 使用正确类型创建切片
const createBearSlice: StateCreator<
BearSlice & FishSlice, // 组合存储类型
[], // 中间件修改器(如果没有则为空)
[], // 链式中间件(如果没有则为空)
BearSlice // 此切片的类型
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
const createFishSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
FishSlice
> = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})
// 组合切片
const useStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))
中间件配置
持久化中间件(localStorage)
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface MyStore {
data: string[]
addItem: (item: string) => void
}
const useStore = create<MyStore>()(
persist(
(set) => ({
data: [],
addItem: (item) => set((state) => ({ data: [...state.data, item] })),
}),
{
name: 'my-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ data: state.data }), // 仅持久化 'data'
},
),
)
Devtools 中间件(Redux DevTools)
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface CounterStore {
count: number
increment: () => void
}
const useStore = create<CounterStore>()(
devtools(
(set) => ({
count: 0,
increment: () =>
set(
(state) => ({ count: state.count + 1 }),
undefined,
'counter/increment', // DevTools 中的动作名称
),
}),
{ name: 'CounterStore' }, // DevTools 中的存储名称
),
)
组合多个中间件
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const useStore = create<MyStore>()(
devtools(
persist(
(set) => ({
// 存储定义
}),
{ name: 'my-storage' },
),
{ name: 'MyStore' },
),
)
顺序重要:devtools(persist(...)) 在 DevTools 中显示持久化动作。
常见模式
模式:计算/派生值
interface StoreWithComputed {
items: string[]
addItem: (item: string) => void
// 在选择器中计算,不存储
}
const useStore = create<StoreWithComputed>()((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}))
// 在组件中使用
function ItemCount() {
const count = useStore((state) => state.items.length)
return <div>{count} items</div>
}
模式:异步动作
interface AsyncStore {
data: string | null
isLoading: boolean
error: string | null
fetchData: () => Promise<void>
}
const useAsyncStore = create<AsyncStore>()((set) => ({
data: null,
isLoading: false,
error: null,
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/data')
const data = await response.text()
set({ data, isLoading: false })
} catch (error) {
set({ error: (error as Error).message, isLoading: false })
}
},
}))
模式:重置存储
interface ResettableStore {
count: number
name: string
increment: () => void
reset: () => void
}
const initialState = {
count: 0,
name: '',
}
const useStore = create<ResettableStore>()((set) => ({
...initialState,
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set(initialState),
}))
模式:带参数的选择器
interface TodoStore {
todos: Array<{ id: string; text: string; done: boolean }>
addTodo: (text: string) => void
toggleTodo: (id: string) => void
}
const useStore = create<TodoStore>()((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: Date.now().toString(), text, done: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
),
})),
}))
// 带参数使用
function Todo({ id }: { id: string }) {
const todo = useStore((state) => state.todos.find((t) => t.id === id))
const toggleTodo = useStore((state) => state.toggleTodo)
if (!todo) return null
return (
<div>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(id)}
/>
{todo.text}
</div>
)
}
使用捆绑资源
模板(templates/)
此技能包括 8 个现成可用的模板文件:
basic-store.ts- 最小 JavaScript 存储示例typescript-store.ts- 正确类型化的 TypeScript 存储persist-store.ts- 带迁移的 localStorage 持久化slices-pattern.ts- 模块化存储组织devtools-store.ts- Redux DevTools 集成nextjs-store.ts- 支持 SSR 的 Next.js 存储与水合computed-store.ts- 派生状态模式async-actions-store.ts- 带加载状态的异步操作
示例用法:
# 复制模板到您的项目
cp ~/.claude/skills/zustand-state-management/templates/typescript-store.ts src/store/
何时使用每个:
- 使用
basic-store.ts用于快速原型 - 使用
typescript-store.ts用于大多数生产应用 - 使用
persist-store.ts当状态需要在页面重载后保留时 - 使用
slices-pattern.ts用于大型、复杂存储(100+ 行) - 使用
nextjs-store.ts用于具有 SSR 的 Next.js 项目
参考(references/)
复杂场景的深度文档:
middleware-guide.md- 完整中间件文档(持久化,devtools,immer,自定义)typescript-patterns.md- 高级 TypeScript 模式和故障排除nextjs-hydration.md- SSR、水合和 Next.js 最佳实践migration-guide.md- 从 Redux、Context API 或 Zustand v4 迁移
Claude 何时应加载这些:
- 当用户询问持久化、devtools 或自定义中间件时,加载
middleware-guide.md - 当遇到复杂类型推断问题时,加载
typescript-patterns.md - 对于 Next.js 特定问题,加载
nextjs-hydration.md - 当从其他状态管理解决方案迁移时,加载
migration-guide.md
脚本(scripts/)
check-versions.sh- 验证 Zustand 版本和兼容性
用法:
cd your-project/
~/.claude/skills/zustand-state-management/scripts/check-versions.sh
高级主题
Vanilla 存储(无 React)
import { createStore } from 'zustand/vanilla'
const store = createStore<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// 订阅更改
const unsubscribe = store.subscribe((state) => {
console.log('Count changed:', state.count)
})
// 获取当前状态
console.log(store.getState().count)
// 更新状态
store.getState().increment()
// 清理
unsubscribe()
自定义中间件
import { StateCreator, StoreMutatorIdentifier } from 'zustand'
type Logger = <T>(
f: StateCreator<T, [], []>,
name?: string,
) => StateCreator<T, [], []>
const logger: Logger = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => {
set(...(a as Parameters<typeof set>))
console.log(`[${name}]:`, get())
}
return f(loggedSet, get, store)
}
// 使用自定义中间件
const useStore = create<MyStore>()(
logger((set) => ({
// 存储定义
}), 'MyStore'),
)
Immer 中间件(可变更新)
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface TodoStore {
todos: Array<{ id: string; text: string }>
addTodo: (text: string) => void
}
const useStore = create<TodoStore>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
// 使用 Immer 直接突变
state.todos.push({ id: Date.now().toString(), text })
}),
})),
)
依赖项
必需:
zustand@5.0.8- 状态管理库react@18.0.0+- React 框架
可选:
@types/node- 用于 TypeScript 路径解析immer- 用于可变更新语法- Redux DevTools 扩展 - 用于 devtools 中间件
官方文档
- Zustand:https://zustand.docs.pmnd.rs/
- GitHub:https://github.com/pmndrs/zustand
- TypeScript 指南:https://zustand.docs.pmnd.rs/guides/typescript
- 切片模式:https://github.com/pmndrs/zustand/blob/main/docs/guides/slices-pattern.md
- Context7 库 ID:
/pmndrs/zustand
包版本(验证于 2025-10-24)
{
"dependencies": {
"zustand": "^5.0.8",
"react": "^19.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.0.0"
}
}
兼容性:
- React 18+,React 19 ✅
- TypeScript 5+ ✅
- Next.js 14+,Next.js 15+ ✅
- Vite 5+ ✅
故障排除
问题:存储更新不触发重渲染
解决方案:确保使用选择器函数,而不是解构:const bears = useStore(state => state.bears) 而非 const { bears } = useStore()
问题:TypeScript 错误与中间件
解决方案:使用双括号:create<T>()() 而非 create<T>()
问题:持久化中间件导致水合错误
解决方案:实现 _hasHydrated 标志模式(见问题 #1)
问题:动作不在 Redux DevTools 中显示
解决方案:将动作名称作为 set 的第三个参数传递:set(newState, undefined, 'actionName')
问题:存储状态意外重置
解决方案:检查是否使用 HMR(热模块替换) - 在开发中,Zustand 会在模块重载时重置
完整设置检查清单
使用此检查清单验证您的 Zustand 设置:
- [ ] 安装了
zustand@5.0.8或更高版本 - [ ] 使用适当的 TypeScript 类型创建存储
- [ ] 使用了
create<T>()()双括号语法 - [ ] 在组件中测试了选择器函数
- [ ] 验证了组件仅在选择的状态更改时重渲染
- [ ] 如果使用持久化:配置了唯一存储名称
- [ ] 如果使用持久化:为 Next.js 实现了水合检查
- [ ] 如果使用 devtools:为调试命名了动作
- [ ] 如果使用切片:为每个切片正确类型化了
StateCreator - [ ] 所有动作都是纯函数
- [ ] 无直接状态突变
- [ ] 存储在生产构建中工作
有问题?问题?
- 查看 references/typescript-patterns.md 获取 TypeScript 帮助
- 查看 references/nextjs-hydration.md 获取 Next.js 问题
- 查看 references/middleware-guide.md 获取持久化/devtools 帮助
- 官方文档:https://zustand.docs.pmnd.rs/
- GitHub 问题:https://github.com/pmndrs/zustand/issues