zustand-state-management
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZustand State Management
Zustand 状态管理
Last Updated: 2026-01-21
Latest Version: zustand@5.0.10 (released 2026-01-12)
Dependencies: React 18-19, TypeScript 5+
最后更新: 2026-01-21
最新版本: zustand@5.0.10(发布于2026-01-12)
依赖: React 18-19, TypeScript 5+
Quick Start
快速开始
bash
npm install zustandTypeScript Store (CRITICAL: use double parentheses):
create<T>()()typescript
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 })),
}))Use in Components:
tsx
const bears = useBearStore((state) => state.bears) // Only re-renders when bears changes
const increase = useBearStore((state) => state.increase)bash
npm install zustandTypeScript 状态仓库(注意:使用双括号):
create<T>()()typescript
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 })),
}))在组件中使用:
tsx
const bears = useBearStore((state) => state.bears) // 仅当bears变化时重新渲染
const increase = useBearStore((state) => state.increase)Core Patterns
核心模式
Basic Store (JavaScript):
javascript
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))TypeScript Store (Recommended):
typescript
interface CounterStore { count: number; increment: () => void }
const useStore = create<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))Persistent Store (survives page reloads):
typescript
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<UserPreferences>()(
persist(
(set) => ({ theme: 'system', setTheme: (theme) => set({ theme }) }),
{ name: 'user-preferences', storage: createJSONStorage(() => localStorage) },
),
)基础状态仓库(JavaScript):
javascript
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))TypeScript 状态仓库(推荐):
typescript
interface CounterStore { count: number; increment: () => void }
const useStore = create<CounterStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))持久化状态仓库(页面刷新后保留状态):
typescript
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<UserPreferences>()(
persist(
(set) => ({ theme: 'system', setTheme: (theme) => set({ theme }) }),
{ name: 'user-preferences', storage: createJSONStorage(() => localStorage) },
),
)Critical Rules
关键规则
Always Do
务必遵循
✅ Use (double parentheses) in TypeScript for middleware compatibility
✅ Define separate interfaces for state and actions
✅ Use selector functions to extract specific state slices
✅ Use with updater functions for derived state:
✅ Use unique names for persist middleware storage keys
✅ Handle Next.js hydration with flag pattern
✅ Use hook for selecting multiple values
✅ Keep actions pure (no side effects except state updates)
create<T>()()setset((state) => ({ count: state.count + 1 }))hasHydrateduseShallow✅ 在TypeScript中使用(双括号)以兼容中间件
✅ 为状态和操作定义独立的接口
✅ 使用选择器函数提取特定状态切片
✅ 对派生状态使用带更新器函数的:
✅ 为持久化中间件的存储键使用唯一名称
✅ 使用标记模式处理Next.js水合
✅ 使用钩子选择多个值
✅ 保持操作纯净(除状态更新外无副作用)
create<T>()()setset((state) => ({ count: state.count + 1 }))hasHydrateduseShallowNever Do
绝对禁止
❌ Use (single parentheses) in TypeScript - breaks middleware types
❌ Mutate state directly: - use immutable updates
❌ Create new objects in selectors: - causes infinite renders
❌ Use same storage name for multiple stores - causes data collisions
❌ Access localStorage during SSR without hydration check
❌ Use Zustand for server state - use TanStack Query instead
❌ Export store instance directly - always export the hook
create<T>(...)set((state) => { state.count++; return state })useStore((state) => ({ a: state.a }))❌ 在TypeScript中使用(单括号)- 会破坏中间件类型
❌ 直接修改状态: - 使用不可变更新
❌ 在选择器中创建新对象: - 会导致无限渲染
❌ 为多个状态仓库使用相同的存储名称 - 会导致数据冲突
❌ 在SSR期间未进行水合检查就访问localStorage
❌ 使用Zustand管理服务端状态 - 改用TanStack Query
❌ 直接导出状态仓库实例 - 始终导出钩子
create<T>(...)set((state) => { state.count++; return state })useStore((state) => ({ a: state.a }))Known Issues Prevention
已知问题预防
This skill prevents 6 documented issues:
本内容可预防6种已记录的问题:
Issue #1: Next.js Hydration Mismatch
问题1:Next.js 水合不匹配
Error: or
"Text content does not match server-rendered HTML""Hydration failed"Source:
- DEV Community: Persist middleware in Next.js
- GitHub Discussions #2839
Why It Happens:
Persist middleware reads from localStorage on client but not on server, causing state mismatch.
Prevention:
typescript
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 />
}错误: 或
"Text content does not match server-rendered HTML""Hydration failed"来源:
- DEV Community: Persist middleware in Next.js
- GitHub Discussions #2839
原因:
持久化中间件在客户端读取localStorage,但在服务端不读取,导致状态不匹配。
解决方法:
typescript
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>加载中...</div>
}
// 现在可以安全渲染持久化状态
return <ActualContent />
}Issue #2: TypeScript Double Parentheses Missing
问题2:TypeScript 双括号缺失
Error: Type inference fails, types break with middleware
StateCreatorWhy It Happens:
The currying syntax is required for middleware to work with TypeScript inference.
create<T>()()Prevention:
typescript
// ❌ WRONG - Single parentheses
const useStore = create<MyStore>((set) => ({
// ...
}))
// ✅ CORRECT - Double parentheses
const useStore = create<MyStore>()((set) => ({
// ...
}))Rule: Always use in TypeScript, even without middleware (future-proof).
create<T>()()错误: 类型推断失败,类型与中间件不兼容
StateCreator原因:
柯里化语法是中间件与TypeScript推断兼容的必要条件。
create<T>()()解决方法:
typescript
// ❌ 错误写法 - 单括号
const useStore = create<MyStore>((set) => ({
// ...
}))
// ✅ 正确写法 - 双括号
const useStore = create<MyStore>()((set) => ({
// ...
}))规则: 即使不使用中间件,在TypeScript中也始终使用(为未来兼容考虑)。
create<T>()()Issue #3: Persist Middleware Import Error
问题3:持久化中间件导入错误
Error:
"Attempted import error: 'createJSONStorage' is not exported from 'zustand/middleware'"Source: GitHub Discussion #2839
Why It Happens:
Wrong import path or version mismatch between zustand and build tools.
Prevention:
typescript
// ✅ CORRECT imports for v5
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
// Verify versions
// zustand@5.0.9 includes createJSONStorage
// zustand@4.x uses different API
// Check your package.json
// "zustand": "^5.0.9"错误:
"Attempted import error: 'createJSONStorage' is not exported from 'zustand/middleware'"来源: GitHub Discussion #2839
原因:
导入路径错误或zustand与构建工具版本不匹配。
解决方法:
typescript
// ✅ v5版本正确导入
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
// 验证版本
// zustand@5.0.9 包含 createJSONStorage
// zustand@4.x 使用不同的API
// 检查你的package.json
// "zustand": "^5.0.9"Issue #4: Infinite Render Loop
问题4:无限渲染循环
Error: Component re-renders infinitely, browser freezes
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.Source:
- GitHub Discussions #2642
- Issue #2863
Why It Happens:
Creating new object references in selectors causes Zustand to think state changed.
v5 Breaking Change: Zustand v5 made this error MORE explicit compared to v4. In v4, this behavior was "non-ideal" but could go unnoticed. In v5, you'll immediately see the "Maximum update depth exceeded" error.
Prevention:
typescript
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 }))
)错误: 组件无限重新渲染,浏览器冻结
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.来源:
- GitHub Discussions #2642
- Issue #2863
原因:
在选择器中创建新对象引用会导致Zustand认为状态已更改。
v5 破坏性变化: 与v4相比,Zustand v5让这个错误更明显。在v4中,这种行为只是“不理想”但可能被忽略;在v5中,你会立即看到“Maximum update depth exceeded”错误。
解决方法:
typescript
import { useShallow } 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 - 使用useShallow钩子选择多个值
const { bears, fishes } = useStore(
useShallow((state) => ({ bears: state.bears, fishes: state.fishes }))
)Issue #5: Slices Pattern TypeScript Complexity
问题5:切片模式 TypeScript 复杂度
Error: types fail to infer, complex middleware types break
StateCreatorSource: Official Slices Pattern Guide
Why It Happens:
Combining multiple slices requires explicit type annotations for middleware compatibility.
Prevention:
typescript
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),
}))错误: 类型推断失败,复杂中间件类型不兼容
StateCreator来源: 官方切片模式指南
原因:
合并多个切片需要显式的类型注解以兼容中间件。
解决方法:
typescript
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),
}))Issue #6: Persist Middleware Race Condition (Fixed v5.0.10+)
问题6:持久化中间件竞态条件(v5.0.10+已修复)
Error: Inconsistent state during concurrent rehydration attempts
Source:
Why It Happens:
In Zustand v5.0.9 and earlier, concurrent calls to rehydrate during persist middleware initialization could cause a race condition where multiple hydration attempts would interfere with each other, leading to inconsistent state.
Prevention:
Upgrade to Zustand v5.0.10 or later. No code changes needed - the fix is internal to the persist middleware.
bash
npm install zustand@latest # Ensure v5.0.10+Note: This was fixed in v5.0.10 (January 2026). If you're using v5.0.9 or earlier and experiencing state inconsistencies with persist middleware, upgrade immediately.
错误: 并发水合尝试期间状态不一致
来源:
原因:
在Zustand v5.0.9及更早版本中,持久化中间件初始化期间的并发水合调用可能导致竞态条件,多个水合尝试相互干扰,导致状态不一致。
解决方法:
升级到Zustand v5.0.10或更高版本。无需修改代码 - 修复在持久化中间件内部实现。
bash
npm install zustand@latest // 确保版本为v5.0.10+注意: 此问题在v5.0.10(2026年1月)中已修复。如果你使用v5.0.9或更早版本且遇到持久化中间件的状态不一致问题,请立即升级。
Middleware
中间件
Persist (localStorage):
typescript
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'
},
),
)Devtools (Redux DevTools):
typescript
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 Migration Note: In Zustand v4, devtools was imported from . In v5, use (as shown above). If you see "Module not found: Can't resolve 'zustand/middleware/devtools'", update your import path.
'zustand/middleware/devtools''zustand/middleware'Combining Middlewares (order matters):
typescript
const useStore = create<MyStore>()(devtools(persist((set) => ({ /* ... */ }), { name: 'storage' }), { name: 'MyStore' }))持久化(localStorage):
typescript
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 }), // 仅持久化'data'
},
),
)开发者工具(Redux DevTools):
typescript
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从导入。在v5中,使用(如上所示)。如果你看到“Module not found: Can't resolve 'zustand/middleware/devtools'”,请更新导入路径。
'zustand/middleware/devtools''zustand/middleware'组合中间件(顺序很重要):
typescript
const useStore = create<MyStore>()(devtools(persist((set) => ({ /* ... */ }), { name: 'storage' }), { name: 'MyStore' }))Common Patterns
常见模式
Computed/Derived Values (in selector, not stored):
typescript
const count = useStore((state) => state.items.length) // Computed on readAsync Actions:
typescript
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 })
},
}))Resetting Store:
typescript
const initialState = { count: 0, name: '' }
const useStore = create<ResettableStore>()((set) => ({
...initialState,
reset: () => set(initialState),
}))Selector with Params:
typescript
const todo = useStore((state) => state.todos.find((t) => t.id === id))计算/派生值(在选择器中,不存储):
typescript
const count = useStore((state) => state.items.length) // 读取时计算异步操作:
typescript
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 })
},
}))重置仓库:
typescript
const initialState = { count: 0, name: '' }
const useStore = create<ResettableStore>()((set) => ({
...initialState,
reset: () => set(initialState),
}))带参数的选择器:
typescript
const todo = useStore((state) => state.todos.find((t) => t.id === id))Bundled Resources
配套资源
Templates: , , , , , , ,
basic-store.tstypescript-store.tspersist-store.tsslices-pattern.tsdevtools-store.tsnextjs-store.tscomputed-store.tsasync-actions-store.tsReferences: (persist/devtools/immer/custom), (type inference issues), (SSR/hydration), (from Redux/Context/v4)
middleware-guide.mdtypescript-patterns.mdnextjs-hydration.mdmigration-guide.mdScripts: (version compatibility)
check-versions.sh模板: , , , , , , ,
basic-store.tstypescript-store.tspersist-store.tsslices-pattern.tsdevtools-store.tsnextjs-store.tscomputed-store.tsasync-actions-store.ts参考文档: (持久化/开发者工具/immer/自定义), (类型推断问题), (SSR/水合), (从Redux/Context/v4迁移)
middleware-guide.mdtypescript-patterns.mdnextjs-hydration.mdmigration-guide.md脚本: (版本兼容性检查)
check-versions.shAdvanced Topics
高级主题
Vanilla Store (Without React):
typescript
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()Custom Middleware:
typescript
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 Middleware (Mutable Updates):
typescript
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 Migration Note: If upgrading from v5.0.3 to v5.0.4+ and immer middleware stops working, verify you're using the import path shown above (). Some users reported issues after the v5.0.4 update that were resolved by confirming the correct import.
zustand/middleware/immerExperimental SSR Safe Middleware (v5.0.9+):
Status: Experimental (API may change)
Zustand v5.0.9 introduced experimental middleware for Next.js usage. This provides an alternative approach to the pattern (see Issue #1).
unstable_ssrSafe_hasHydratedtypescript
import { unstable_ssrSafe } from 'zustand/middleware'
const useStore = create<Store>()(
unstable_ssrSafe(
persist(
(set) => ({ /* state */ }),
{ name: 'my-store' }
)
)
)Recommendation: Continue using the pattern documented in Issue #1 until this API stabilizes. Monitor Discussion #2740 for updates on when this becomes stable.
_hasHydrated无React的Vanilla仓库:
typescript
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()自定义中间件:
typescript
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中间件(可变更新):
typescript
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中间件无法工作,请验证你使用的是上述导入路径()。部分用户反馈v5.0.4更新后出现问题,确认正确导入路径即可解决。
zustand/middleware/immer实验性SSR安全中间件(v5.0.9+):
状态: 实验性(API可能变更)
Zustand v5.0.9引入了实验性的中间件,用于Next.js场景。这是模式的替代方案(见问题1)。
unstable_ssrSafe_hasHydratedtypescript
import { unstable_ssrSafe } from 'zustand/middleware'
const useStore = create<Store>()(
unstable_ssrSafe(
persist(
(set) => ({ /* 状态 */ }),
{ name: 'my-store' }
)
)
)建议: 在该API稳定前,继续使用问题1中记录的模式。关注Discussion #2740获取稳定更新。
_hasHydratedOfficial Documentation
官方文档
- Zustand: https://zustand.docs.pmnd.rs/
- GitHub: https://github.com/pmndrs/zustand
- TypeScript Guide: https://zustand.docs.pmnd.rs/guides/typescript
- Context7 Library ID:
/pmndrs/zustand
- Zustand: https://zustand.docs.pmnd.rs/
- GitHub: https://github.com/pmndrs/zustand
- TypeScript指南: https://zustand.docs.pmnd.rs/guides/typescript
- Context7库ID:
/pmndrs/zustand