使用Zustand状态管理
使用Zustand状态管理
选型原因
相比于Redux,Zustand更加轻量、直观,没有那么多规矩,比较适合小项目。由于我之前跟着百战网课用过redux了,这次就尝试一个新的管理库。最开始我想用Recoil,谁知这个早就停止维护了,不支持react-19,所以改用Zustand。
| 维度 | Zustand | Redux |
|---|---|---|
| 设计哲学 | 极简、hook-first | 严格的单向数据流 |
| 心智模型 | 就像写普通 JS 状态 | Action → Reducer → Store |
| 样板代码 | 极少 | 非常多(即使用 RTK 也不少) |
| 是否强制不可变 | 不强制 | 强制(通过 reducer) |
在写法上,Zustand也比redux简单非常多,它几乎和useState一样用:
const useStore = create((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 })),
}));
// 组件中:
const count = useStore((s) => s.count);对比redux的reducer写法:
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
inc(state) {
state.count += 1;
},
},
});之后还需要store、provider、selector、dispatch……
而且,Zustand组件只会在“自己关心的状态变化时”重渲染(细粒度刷新),默认避免了Redux的误渲染(以前手动用memo解决)。另外的有点(相比redux),包括:可以直接用async写异步,不需要thunk等中间件、支持多store、在TS中类型推导比较强,无需手写,泛型复杂度低,无需action类型……
当然,约束少意味着需要开发者自律,在大项目中最好还是用redux了。
数据流
考虑如下业务场景:点击添加按钮后显示弹窗。如果使用全局状态管理,只需在按钮回调处把显示弹窗变量modalMode的值设为显示:
const openAdd = useMenuUIStore((state) => state.openAdd);组件中用三元运算符控制:
{modalMode === 'add'
? (
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
{/* ... */}可以看出,对于单一变量的改变只需要一个自定义的useMenuUIStore hook。
首先要定义出变量以及hook的类型方便推断:
export type MenuModalMode = 'add' | 'edit' | null;
interface MenuUIState {
modalOpen: boolean;
modalMode: MenuModalMode;
openAdd: () => void;
openEdit: () => void;
close: () => void;
}在当前场景中,“编辑”和“添加”状态下,弹窗都要开启,只有“关闭(null)”状态下弹窗才不显示,所以用两个变量表示,把它们和三个状态都写在hook类型中。
实现时:
export const useMenuUIStore = create<MenuUIState>((set) => ({
modalOpen: false,
modalMode: null,
openAdd: () =>
set({
modalOpen: true,
modalMode: 'add',
}),
openEdit: () =>
set({
modalOpen: true,
modalMode: 'edit',
}),
close: () =>
set({
modalOpen: false,
modalMode: null,
}),
}));我这里的方法都没有返回值,实际在改变状态后也可以返回东西。
特别注意
最开始在组件中使用状态时,我并没有分开写,而是采用多字段订阅:
const { modalOpen, modalMode, close } = useMenuUIStore((state) => ({
modalOpen: state.modalOpen,
modalMode: state.modalMode,
close: state.close,
}));遇到了如下的报错
The result of getSnapshot should be cached to avoid an infinite loop❓ 为什么会出错?
关键点 1:Zustand 基于 useSyncExternalStore
React 18+ / 19 对 snapshot 稳定性要求极高,React 会比较 前后 snapshot 是否相同。
关键点 2:selector 返回了「新对象」
(state) => ({ ... })- 每次 render → 新对象
Object.is(prev, next) === false- React 认为状态一直在变
- → 触发 无限更新保护
