Zustand 系统学习大纲(实战版)

1️⃣ 基础知识

1.1 安装

npm install zustand

使用场景

  • 任何 React 项目中使用轻量级全局状态管理

优化建议

  • 不需要单独安装中间件,所有插件均内置

1.2 创建 store

import create from 'zustand'
interface CounterState {
count: number
increment: () =>
void
}
export const useCounterStore = create<CounterState>((set) =>
  ({
  count: 0,
  increment: () =>
  set((state) =>
  ({ count: state.count + 1
  }))
  }))

使用场景

  • 管理单个模块状态,如计数器、表单状态

优化建议

  • 拆分 store 模块,避免单 store 过大
  • 使用 TS 类型约束,避免动态访问属性报错

1.3 声明属性

interface StoreState {
name: string
items: string[]
loading: boolean
}

使用场景

  • 存储基本类型、数组、对象、异步状态

优化建议

  • 对象/数组修改使用 setter 或 immer 避免引用丢失
  • 异步状态建议添加 loading / error 字段

1.4 声明方法

setName: (newName: string) =>
set({ name: newName
})
fetchItems: async () =>
{
set({ loading: true
})
try {
const res = await fetch('/api/items')
const data = await res.json()
set({ items: data
})
} catch (e) {
console.error(e)
} finally {
set({ loading: false
})
}
}

使用场景

  • 同步修改字段
  • 异步请求更新状态

优化建议

  • 异步方法需处理 loading / error
  • 避免直接修改对象引用

2️⃣ 页面中使用 store

2.1 单字段订阅

const count = useCounterStore(state => state.count)

使用场景

  • 页面只依赖单个字段
    优化建议
  • 精准订阅字段,减少组件重渲染

2.2 解构整个 store

const { count, increment
} = useCounterStore()

使用场景

  • 页面需要同时使用多个字段和方法
    优化建议
  • 小心对象引用变化导致整组件刷新

2.3 批量订阅

const [count, items] = useCounterStore(state =>
[state.count, state.items])

使用场景

  • 需要同时获取多个字段
    优化建议
  • 只订阅必要字段
  • 对象/数组变化会触发刷新

2.4 非组件中获取

useCounterStore.getState().increment()
const currentCount = useCounterStore.getState().count

使用场景

  • 在 utils 或事件回调中访问 store
    优化建议
  • 避免绕过 React 响应式

2.5 订阅变化

useCounterStore.subscribe(
state => state.count,
newCount =>
console.log('count updated', newCount)
)

使用场景

  • 需要监听某字段变化进行副作用
    优化建议
  • 精准订阅单字段,避免全局订阅

3️⃣ 插件系统(Middleware)

3.1 DevTools

import { devtools
} from 'zustand/middleware'
const useStore = create(devtools((set) =>
({
count: 0,
increment: () =>
set(state =>
({ count: state.count + 1
}))
}), { name: 'counter'
}))

使用场景

  • 调试状态变化,查看 action 历史

优化建议

  • 为每个 action 自定义名称
  • 仅在开发环境启用 DevTools

3.2 Persist

import { persist
} from 'zustand/middleware'
const useStore = create(
persist(
(set) =>
({ count: 0, increment: () =>
set(state =>
({ count: state.count + 1
}))
}),
{ name: 'counter-storage', getStorage: () => localStorage
}
)
)

使用场景

  • 页面刷新后仍需保持状态
  • 用户数据、表单输入缓存

优化建议

  • 使用 partialize 选择性持久化
  • 避免大对象直接写入 localStorage

3.3 Immer

import { immer
} from 'zustand/middleware/immer'
const useStore = create(
immer((set) =>
({
items: [] as string[],
addItem: (item: string) =>
set(state =>
{ state.items.push(item)
})
}))
)

使用场景

  • 对对象/数组直接修改而不破坏响应式

优化建议

  • 避免频繁创建新对象,提升性能

3.4 多中间件组合

import { devtools, persist, immer
} from 'zustand/middleware'
const useStore = create(
devtools(
persist(
immer((set) =>
({
count: 0,
increment: () =>
set(state =>
{ state.count += 1
})
})),
{ name: 'counter-storage'
}
)
)
)

使用场景

  • 同时需要 DevTools、持久化和 Immer

优化建议

  • 中间件顺序重要:immer → persist → devtools
  • 避免重复包装

4️⃣ 优化与升级

4.1 派生状态

const doubleCount = useCounterStore(state => state.count * 2)

使用场景

  • 页面展示需要派生数据
    优化建议
  • 不存 store,避免冗余
  • 可用 useMemo 缓存计算

4.2 精准订阅

const itemCount = useStore(state => state.items.length)

使用场景

  • 对象/数组变化避免全量刷新
    优化建议
  • 对数组/对象修改使用 Immer 或新对象替换

4.3 多 store 组合

  • 拆模块,按业务功能管理状态
  • 页面按需引入

优化建议

  • 避免单 store 过大
  • 多 store 更易扩展和维护

4.4 TypeScript 类型约束

interface CarbonData { total: number; categories: string[]
}
interface StoreState { carbonData: CarbonData;
setCarbonData: (data: CarbonData) =>
void
}

使用场景

  • 确保状态与方法类型安全

优化建议

  • 明确所有字段和方法类型,避免运行时错误

4.5 持久化选择性字段

partialize: (state) =>
({ carbonData: state.carbonData
})

使用场景

  • 只存储必要数据
    优化建议
  • 减少 localStorage 占用
  • 可结合 redux-devtools 调试

4.6 异步状态管理

  • 管理 loading/error
  • 缓存 API 数据
  • 避免直接在组件中 fetch

优化建议

  • 使用 store 封装 API 请求
  • 返回类型统一,方便组件使用

posted on 2025-09-24 09:32  ycfenxi  阅读(13)  评论(0)    收藏  举报