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 请求
- 返回类型统一,方便组件使用