关于 zustand-state-management
面向 React 和 TypeScript 开发者的 Zustand 状态管理指南,涵盖 store 创建、选择器、中间件及常见问题处理。还提供持久化、Next.js 水合、异步操作等模式与模板。
主要功能
- 01JavaScript 与 TypeScript store 模式
- 02选择器与状态更新建议
- 03持久化和 Devtools 中间件
- 04Next.js 水合问题处理
- 05异步操作与派生值模式
- 06迁移指南及代码模板
适用场景
- 01为 React 应用设计状态 store
- 02设置持久化状态与 Devtools
- 03处理 Next.js 水合不匹配
- 04排查选择器导致的重复渲染
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill zustand-state-management安装后在 Claude Code 中输入/zustand-state-management确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonassets/example-template.txtreferences/example-reference.mdmiddleware-guide.mdmigration-guide.mdnextjs-hydration.mdtypescript-patterns.mdrules/zustand-state-management.mdscripts/check-versions.shexample-script.shtemplates/async-actions-store.tsbasic-store.tscomputed-store.tsdevtools-store.tsnextjs-store.tspersist-store.tsslices-pattern.tstypescript-store.ts详细介绍
Zustand 状态管理
最后更新:2026-01-21 最新版本:[email protected](发布于 2026-01-12) 依赖项:React 18-19、TypeScript 5+
快速开始
npm install zustand
TypeScript Store(重要:使用 create<T>()() 双括号):
import { create } from 'zustand'
interface BearStore {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearStore>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
在组件中使用:
const bears = useBearStore((state) => state.bears) // Only re-renders when bears changes
const increase = useBearStore((state) => state.increase)
核心模式
基本 Store(JavaScript):
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
TypeScript Store(推荐):
interface CounterStore { count: number; increment: () => void }
const useStore = create<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
持久化 Store(页面重新加载后仍然保留):
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<UserPreferences>()(
persist(
(set) => ({ theme: 'system', setTheme: (theme) => set({ theme }) }),
{ name: 'user-preferences', storage: createJSONStorage(() => localStorage) },
),
)
重要规则
始终要做
✅ 在 TypeScript 中使用 create<T>()()(双括号)以兼容中间件
✅ 为状态和操作分别定义接口
✅ 使用选择器函数提取特定的状态片段
✅ 对派生状态使用带更新函数的 set:set((state) => ({ count: state.count + 1 }))
✅ 为 persist 中间件的存储键使用唯一名称
✅ 使用 hasHydrated 标志模式处理 Next.js 水合
✅ 使用 useShallow 钩子选择多个值
✅ 保持操作纯净(除状态更新外不产生副作用)
绝不要做
❌ 在 TypeScript 中使用 create<T>(...)(单括号)——会破坏中间件类型
❌ 直接修改状态:set((state) => { state.count++; return state })——应使用不可变更新
❌ 在选择器中创建新对象:useStore((state) => ({ a: state.a }))——会导致无限次渲染
❌ 多个 Store 使用相同的存储名称——会造成数据冲突
❌ 未检查水合状态就在 SSR 期间访问 localStorage
❌ 使用 Zustand 管理服务端状态——应改用 TanStack Query
❌ 直接导出 Store 实例——始终导出钩子
已知问题预防
此技能可预防 6 个已记录的问题:
问题 #1:Next.js 水合不匹配
错误:"Text content does not match server-rendered HTML" 或 "Hydration failed"
来源:
- DEV Community:Next.js 中的 Persist 中间件
- GitHub 讨论 #2839
原因: Persist 中间件会在客户端从 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)
},
},
),
)
// In component
function MyComponent() {
const hasHydrated = useStore((state) => state._hasHydrated)
if (!hasHydrated) {
return <div>Loading...</div>
}
// Now safe to render with persisted state
return <ActualContent />
}
问题 #2:遗漏 TypeScript 双括号
错误:类型推断失败,StateCreator 类型与中间件不兼容
原因:
中间件要与 TypeScript 类型推断配合工作,必须使用柯里化语法 create<T>()()。
预防方法:
// ❌ WRONG - Single parentheses
const useStore = create<MyStore>((set) => ({
// ...
}))
// ✅ CORRECT - Double parentheses
const useStore = create<MyStore>()((set) => ({
// ...
}))
规则:在 TypeScript 中始终使用 create<T>()(),即使没有中间件也一样(以确保未来兼容)。
问题 #3:Persist 中间件导入错误
错误:"Attempted import error: 'createJSONStorage' is not exported from 'zustand/middleware'"
来源:GitHub 讨论 #2839
原因: 导入路径错误,或 zustand 与构建工具之间的版本不匹配。
预防方法:
// ✅ CORRECT imports for v5
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
// Verify versions
// [email protected] includes createJSONStorage
// [email protected] uses different API
// Check your package.json
// "zustand": "^5.0.9"
问题 #4:无限渲染循环
错误:组件不断重新渲染,浏览器冻结
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
来源:
- GitHub 讨论 #2642
- 问题 #2863
原因: 在选择器中创建新的对象引用,会让 Zustand 认为状态已更改。
v5 重大变更:与 v4 相比,Zustand v5 会更明确地暴露此错误。在 v4 中,这种行为虽然“并不理想”,但可能不易被察觉。在 v5 中,你会立即看到 "Maximum update depth exceeded" 错误。
预防方法:
import { useShallow } from 'zustand/shallow'
// ❌ WRONG - Creates new object every time
const { bears, fishes } = useStore((state) => ({
bears: state.bears,
fishes: state.fishes,
}))
// ✅ CORRECT Option 1 - Select primitives separately
const bears = useStore((state) => state.bears)
const fishes = useStore((state) => state.fishes)
// ✅ CORRECT Option 2 - Use useShallow hook for multiple values
const { bears, fishes } = useStore(
useShallow((state) => ({ bears: state.bears, fishes: state.fishes }))
)
问题 #5:Slices 模式的 TypeScript 复杂性
错误:StateCreator 类型推断失败,复杂的中间件类型无法正常工作
原因: 组合多个切片时,需要显式标注类型才能兼容中间件。
预防方法:
import { create, StateCreator } from 'zustand'
// Define slice types
interface BearSlice {
bears: number
addBear: () => void
}
interface FishSlice {
fishes: number
addFish: () => void
}
// Create slices with proper types
const createBearSlice: StateCreator<
BearSlice & FishSlice, // Combined store type
[], // Middleware mutators (empty if none)
[], // Chained middleware (empty if none)
BearSlice // This slice's type
> = (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 })),
})
// Combine slices
const useStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))
Issue #6:持久化中间件竞态条件(已在 v5.0.10+ 修复)
错误:并发重新水合尝试期间状态不一致
来源:
发生原因: 在 Zustand v5.0.9 及更早版本中,持久化中间件初始化期间并发调用重新水合,可能导致竞态条件:多个水合尝试相互干扰,造成状态不一致。
预防方法: 升级到 Zustand v5.0.10 或更高版本。无需更改代码——此修复已在持久化中间件内部实现。
npm install zustand@latest # Ensure v5.0.10+
注意:此问题已在 v5.0.10 中修复(2026 年 1 月)。如果你使用的是 v5.0.9 或更早版本,并且持久化中间件导致状态不一致,请立即升级。
中间件
持久化(localStorage):
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<MyStore>()(
persist(
(set) => ({ data: [], addItem: (item) => set((state) => ({ data: [...state.data, item] })) }),
{
name: 'my-storage',
partialize: (state) => ({ data: state.data }), // Only persist 'data'
},
),
)
开发者工具(Redux DevTools):
import { devtools } from 'zustand/middleware'
const useStore = create<CounterStore>()(
devtools(
(set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 }), undefined, 'increment') }),
{ name: 'CounterStore' },
),
)
v4→v5 迁移说明:在 Zustand v4 中,devtools 从 'zustand/middleware/devtools' 导入。在 v5 中,请使用 'zustand/middleware'(如上所示)。如果看到 “Module not found: Can't resolve 'zustand/middleware/devtools'”,请更新导入路径。
组合中间件(顺序很重要):
const useStore = create<MyStore>()(devtools(persist((set) => ({ /* ... */ }), { name: 'storage' }), { name: 'MyStore' }))
常见模式
计算值/派生值(在选择器中计算,而非存储):
const count = useStore((state) => state.items.length) // Computed on read
异步操作:
const useAsyncStore = create<AsyncStore>()((set) => ({
data: null,
isLoading: false,
fetchData: async () => {
set({ isLoading: true })
const response = await fetch('/api/data')
set({ data: await response.text(), isLoading: false })
},
}))
重置 Store:
const initialState = { count: 0, name: '' }
const useStore = create<ResettableStore>()((set) => ({
...initialState,
reset: () => set(initialState),
}))
带参数的选择器:
const todo = useStore((state) => state.todos.find((t) => t.id === id))
随附资源
模板:basic-store.ts、typescript-store.ts、persist-store.ts、slices-pattern.ts、devtools-store.ts、nextjs-store.ts、computed-store.ts、async-actions-store.ts
参考资料:middleware-guide.md(persist/devtools/immer/custom)、typescript-patterns.md(类型推断问题)、nextjs-hydration.md(SSR/水合)、migration-guide.md(从 Redux/Context/v4 迁移)
脚本:check-versions.sh(版本兼容性)
高级主题
原生 Store(不使用 React):
import { createStore } from 'zustand/vanilla'
const store = createStore<CounterStore>()((set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) }))
const unsubscribe = store.subscribe((state) => console.log(state.count))
store.getState().increment()
自定义中间件:
const logger: Logger = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => { set(...a); console.log(`[${name}]:`, get()) }
return f(loggedSet, get, store)
}
Immer 中间件(可变更新):
import { immer } from 'zustand/middleware/immer'
const useStore = create<TodoStore>()(immer((set) => ({
todos: [],
addTodo: (text) => set((state) => { state.todos.push({ id: Date.now().toString(), text }) }),
})))
v5.0.3→v5.0.4 迁移说明:如果从 v5.0.3 升级到 v5.0.4+ 后 immer 中间件停止工作,请确认使用的是上方所示的导入路径(zustand/middleware/immer)。一些用户报告称,确认导入路径正确后,v5.0.4 更新后遇到的问题得到了解决。
实验性 SSR 安全中间件(v5.0.9+):
状态:实验性(API 可能会更改)
Zustand v5.0.9 引入了实验性的 unstable_ssrSafe 中间件,供 Next.js 使用。这为 _hasHydrated 模式提供了另一种方案(参见 Issue #1)。
import { unstable_ssrSafe } from 'zustand/middleware'
const useStore = create<Store>()(
unstable_ssrSafe(
persist(
(set) => ({ /* state */ }),
{ name: 'my-store' }
)
)
)
建议:在此 API 稳定之前,继续使用 Issue #1 中介绍的 _hasHydrated 模式。请关注 Discussion #2740,了解该 API 何时稳定的最新消息。
官方文档
- Zustand:https://zustand.docs.pmnd.rs/
- GitHub:https://github.com/pmndrs/zustand
- TypeScript 指南:https://zustand.docs.pmnd.rs/guides/typescript
- Context7 Library ID:
/pmndrs/zustand