mcpField
Z
zustand-state-management

zustand-state-management

github.com/jackspace开发1665 次安装13 周前更新
代码仓库

关于 zustand-state-management

面向 React 项目的 Zustand 状态管理 Claude 技能,提供 TypeScript、持久化、中间件和 Next.js 水合指导,并附带模板与故障排查说明。

主要功能

  1. 01基础与类型化 Store 模式
  2. 02选择器与不可变更新
  3. 03持久化和 DevTools 中间件
  4. 04Next.js 水合指导
  5. 05模板与故障排查

适用场景

  1. 01在 React 应用中创建 Zustand Store
  2. 02让偏好设置在重新加载后保留
  3. 03排查 TypeScript 或渲染循环问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jackspace/claudeskillz --skill zustand-state-management

安装后在 Claude Code 中输入/zustand-state-management确认已加载。

附带文件README.mdSKILL.json

详细介绍

Zustand 状态管理

状态:已可用于生产环境 ✅ 最后更新:2025-10-24 最新版本:[email protected] 依赖项:React 18+、TypeScript 5+


快速入门(3 分钟)

1. 安装 Zustand
npm install zustand
# or
pnpm add zustand
# or
yarn add zustand

为什么选择 Zustand?

  • API 精简:只需学习 1 个函数(create)
  • 无样板代码:无需 Provider、reducer 或 action
  • TypeScript 优先:类型推断出色
  • 速度快:细粒度订阅可避免不必要的重新渲染
  • 灵活:提供用于持久化、开发者工具等功能的中间件
2. 创建你的第一个 Store(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. 在组件中使用 Store
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 Provider
  • 选择器函数可提取特定的状态切片

3 种模式的设置流程

模式 1:基础 Store(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 Store(推荐)

适用于需要类型安全的生产应用:

import { create } from 'zustand'

// Define store interface
interface CounterStore {
  count: number
  increment: () => void
  decrement: () => void
}

// Create typed store
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:持久化 Store

适用于需要在页面重新加载后保留的状态:

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', // unique name in localStorage
      storage: createJSONStorage(() => localStorage), // optional: defaults to localStorage
    },
  ),
)

这很重要,因为:

  • 状态会自动保存到 localStorage
  • 页面重新加载后会恢复状态
  • 也支持 sessionStorage
  • 自动处理序列化

关键规则

始终遵循

✅ 在 TypeScript 中使用中间件时,使用 create<T>()()(双括号) ✅ 为状态和操作分别定义接口 ✅ 使用选择器函数提取特定的状态切片 ✅ 对派生状态使用带更新函数的 set:set((state) => ({ count: state.count + 1 })) ✅ 为 persist 中间件存储键使用唯一名称 ✅ 使用 hasHydrated 标志模式处理 Next.js 水合 ✅ 选择多个值时使用 shallow ✅ 保持操作纯净(除更新状态外,不执行副作用)

绝不这样做

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


预防已知问题

此技能可预防 5 个有记录的问题:

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

错误:"Text content does not match server-rendered HTML" 或 "Hydration failed"

来源:

发生原因: 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 类型与中间件不兼容

来源:Zustand 官方 TypeScript 指南

发生原因: 中间件需要使用柯里化语法 create<T>()(),才能与 TypeScript 类型推断配合工作。

预防方法:

// ❌ 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 Discussion #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.8"
问题 #4:无限渲染循环

错误:组件无限重新渲染,浏览器冻结

来源:GitHub 讨论 #2642

原因: 在选择器中创建新的对象引用会导致 Zustand 认为状态已更改。

预防措施:

import { shallow } 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 shallow for multiple values
const { bears, fishes } = useStore(
  (state) => ({ bears: state.bears, fishes: state.fishes }),
  shallow,
)
问题 #5:切片模式的 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),
}))

中间件配置

Persist 中间件(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 }), // Only persist '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', // Action name in DevTools
        ),
    }),
    { name: 'CounterStore' }, // Store name in DevTools
  ),
)
组合多个中间件
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

const useStore = create<MyStore>()(
  devtools(
    persist(
      (set) => ({
        // store definition
      }),
      { name: 'my-storage' },
    ),
    { name: 'MyStore' },
  ),
)

顺序很重要:devtools(persist(...)) 会在 DevTools 中显示持久化操作。


常见模式

模式:计算值/派生值
interface StoreWithComputed {
  items: string[]
  addItem: (item: string) => void
  // Computed in selector, not stored
}

const useStore = create<StoreWithComputed>()((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}))

// Use in component
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 })
    }
  },
}))
模式:重置 Store
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
      ),
    })),
}))

// Use with parameter
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 Store 示例
  • typescript-store.ts - 类型标注完善的 TypeScript Store
  • persist-store.ts - 带迁移功能的 localStorage 持久化
  • slices-pattern.ts - 模块化 Store 组织方式
  • devtools-store.ts - Redux DevTools 集成
  • nextjs-store.ts - 支持水合且兼容 SSR 的 Next.js Store
  • computed-store.ts - 派生状态模式
  • async-actions-store.ts - 带加载状态的异步操作

使用示例:

# Copy template to your project
cp ~/.claude/skills/zustand-state-management/templates/typescript-store.ts src/store/

适用场景:

  • 快速构建原型时,使用 basic-store.ts
  • 大多数生产应用中,使用 typescript-store.ts
  • 状态需要在页面重新加载后保留时,使用 persist-store.ts
  • 大型复杂 Store(100 行以上)中,使用 slices-pattern.ts
  • 在使用 SSR 的 Next.js 项目中,使用 nextjs-store.ts
参考文档(references/)

适用于复杂场景的深入文档:

  • middleware-guide.md - 完整的中间件文档(persist、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 Store(不使用 React)
import { createStore } from 'zustand/vanilla'

const store = createStore<CounterStore>()((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

// Subscribe to changes
const unsubscribe = store.subscribe((state) => {
  console.log('Count changed:', state.count)
})

// Get current state
console.log(store.getState().count)

// Update state
store.getState().increment()

// Cleanup
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)
}

// Use custom middleware
const useStore = create<MyStore>()(
  logger((set) => ({
    // store definition
  }), '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) => {
        // Mutate directly with Immer
        state.todos.push({ id: Date.now().toString(), text })
      }),
  })),
)

依赖项

必需:

可选:

  • @types/node - 用于 TypeScript 路径解析
  • immer - 用于可变更新语法
  • Redux DevTools Extension - 用于 devtools 中间件

官方文档


软件包版本(已于 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+ ✅

故障排除

问题:Store 更新不会触发重新渲染

解决方案:确保使用选择器函数,而不是解构:const bears = useStore(state => state.bears),而不是 const { bears } = useStore()

问题:使用中间件时出现 TypeScript 错误

解决方案:使用双括号:create<T>()(),而不是 create<T>()

问题:Persist 中间件导致 hydration 错误

解决方案:实现 _hasHydrated 标志模式(参见 Issue #1)

问题:操作未显示在 Redux DevTools 中

解决方案:将操作名称作为第三个参数传给 set:set(newState, undefined, 'actionName')

问题:Store 状态意外重置

解决方案:检查是否使用了 HMR(热模块替换)——开发环境中模块重新加载时 Zustand 会重置


完整设置检查清单

使用此检查清单验证你的 Zustand 设置:

  • 已安装 [email protected] 或更高版本
  • 已使用正确的 TypeScript 类型创建 Store
  • 已使用 create<T>()() 双括号语法
  • 已在组件中测试选择器函数
  • 已验证组件仅在所选状态发生变化时重新渲染
  • 如果使用 persist:已配置唯一的存储名称
  • 如果使用 persist:已为 Next.js 实现 hydration 检查
  • 如果使用 devtools:已为调试命名操作
  • 如果使用 slices:已为每个 slice 正确设置 StateCreator 类型
  • 所有操作都是纯函数
  • 没有直接修改状态
  • Store 在生产构建中正常工作

有疑问?遇到问题?

  1. 查看 references/typescript-patterns.md 获取 TypeScript 帮助
  2. 查看 references/nextjs-hydration.md 获取 Next.js 问题的帮助
  3. 查看 references/middleware-guide.md 获取 persist/devtools 相关帮助
  4. 官方文档:https://zustand.docs.pmnd.rs/
  5. GitHub 问题:https://github.com/pmndrs/zustand/issues

评论

支持 Markdown