zustand-state-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Zustand 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 zustand
TypeScript Store (CRITICAL: use
create<T>()()
double parentheses):
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 zustand
TypeScript 状态仓库(注意:使用
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
create<T>()()
(double parentheses) in TypeScript for middleware compatibility ✅ Define separate interfaces for state and actions ✅ Use selector functions to extract specific state slices ✅ Use
set
with updater functions for derived state:
set((state) => ({ count: state.count + 1 }))
✅ Use unique names for persist middleware storage keys ✅ Handle Next.js hydration with
hasHydrated
flag pattern ✅ Use
useShallow
hook for selecting multiple values ✅ Keep actions pure (no side effects except state updates)
✅ 在TypeScript中使用
create<T>()()
(双括号)以兼容中间件 ✅ 为状态和操作定义独立的接口 ✅ 使用选择器函数提取特定状态切片 ✅ 对派生状态使用带更新器函数的
set
set((state) => ({ count: state.count + 1 }))
✅ 为持久化中间件的存储键使用唯一名称 ✅ 使用
hasHydrated
标记模式处理Next.js水合 ✅ 使用
useShallow
钩子选择多个值 ✅ 保持操作纯净(除状态更新外无副作用)

Never Do

绝对禁止

❌ Use
create<T>(...)
(single parentheses) in TypeScript - breaks middleware types ❌ Mutate state directly:
set((state) => { state.count++; return state })
- use immutable updates ❌ Create new objects in selectors:
useStore((state) => ({ a: state.a }))
- 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

❌ 在TypeScript中使用
create<T>(...)
(单括号)- 会破坏中间件类型 ❌ 直接修改状态:
set((state) => { state.count++; return state })
- 使用不可变更新 ❌ 在选择器中创建新对象:
useStore((state) => ({ a: state.a }))
- 会导致无限渲染 ❌ 为多个状态仓库使用相同的存储名称 - 会导致数据冲突 ❌ 在SSR期间未进行水合检查就访问localStorage ❌ 使用Zustand管理服务端状态 - 改用TanStack Query ❌ 直接导出状态仓库实例 - 始终导出钩子

Known Issues Prevention

已知问题预防

This skill prevents 6 documented issues:
本内容可预防6种已记录的问题:

Issue #1: Next.js Hydration Mismatch

问题1:Next.js 水合不匹配

Error:
"Text content does not match server-rendered HTML"
or
"Hydration failed"
Source:
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"
来源:
原因: 持久化中间件在客户端读取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,
StateCreator
types break with middleware
Why It Happens: The currying syntax
create<T>()()
is required for middleware to work with TypeScript inference.
Prevention:
typescript
// ❌ WRONG - Single parentheses
const useStore = create<MyStore>((set) => ({
  // ...
}))

// ✅ CORRECT - Double parentheses
const useStore = create<MyStore>()((set) => ({
  // ...
}))
Rule: Always use
create<T>()()
in TypeScript, even without middleware (future-proof).
错误: 类型推断失败,
StateCreator
类型与中间件不兼容
原因: 柯里化语法
create<T>()()
是中间件与TypeScript推断兼容的必要条件。
解决方法:
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:
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.
来源:
原因: 在选择器中创建新对象引用会导致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:
StateCreator
types fail to infer, complex middleware types break
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
'zustand/middleware/devtools'
. In v5, use
'zustand/middleware'
(as shown above). If you see "Module not found: Can't resolve 'zustand/middleware/devtools'", update your import path.
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从
'zustand/middleware/devtools'
导入。在v5中,使用
'zustand/middleware'
(如上所示)。如果你看到“Module not found: Can't resolve 'zustand/middleware/devtools'”,请更新导入路径。
组合中间件(顺序很重要):
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 read
Async 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.ts
,
typescript-store.ts
,
persist-store.ts
,
slices-pattern.ts
,
devtools-store.ts
,
nextjs-store.ts
,
computed-store.ts
,
async-actions-store.ts
References:
middleware-guide.md
(persist/devtools/immer/custom),
typescript-patterns.md
(type inference issues),
nextjs-hydration.md
(SSR/hydration),
migration-guide.md
(from Redux/Context/v4)
Scripts:
check-versions.sh
(version compatibility)

模板:
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
(持久化/开发者工具/immer/自定义),
typescript-patterns.md
(类型推断问题),
nextjs-hydration.md
(SSR/水合),
migration-guide.md
(从Redux/Context/v4迁移)
脚本:
check-versions.sh
(版本兼容性检查)

Advanced 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 (
zustand/middleware/immer
). Some users reported issues after the v5.0.4 update that were resolved by confirming the correct import.
Experimental SSR Safe Middleware (v5.0.9+):
Status: Experimental (API may change)
Zustand v5.0.9 introduced experimental
unstable_ssrSafe
middleware for Next.js usage. This provides an alternative approach to the
_hasHydrated
pattern (see Issue #1).
typescript
import { unstable_ssrSafe } from 'zustand/middleware'

const useStore = create<Store>()(
  unstable_ssrSafe(
    persist(
      (set) => ({ /* state */ }),
      { name: 'my-store' }
    )
  )
)
Recommendation: Continue using the
_hasHydrated
pattern documented in Issue #1 until this API stabilizes. Monitor Discussion #2740 for updates on when this becomes stable.

无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中间件无法工作,请验证你使用的是上述导入路径(
zustand/middleware/immer
)。部分用户反馈v5.0.4更新后出现问题,确认正确导入路径即可解决。
实验性SSR安全中间件(v5.0.9+):
状态: 实验性(API可能变更)
Zustand v5.0.9引入了实验性的
unstable_ssrSafe
中间件,用于Next.js场景。这是
_hasHydrated
模式的替代方案(见问题1)。
typescript
import { unstable_ssrSafe } from 'zustand/middleware'

const useStore = create<Store>()(
  unstable_ssrSafe(
    persist(
      (set) => ({ /* 状态 */ }),
      { name: 'my-store' }
    )
  )
)
建议: 在该API稳定前,继续使用问题1中记录的
_hasHydrated
模式。关注Discussion #2740获取稳定更新。

Official Documentation

官方文档