状态更新和合并机制
Zustand 的更新路径:set / setState → 合并或替换 → Object.is 短路 → 通知所有 listener。掌握 partial vs replace 与 浅合并边界 可避免「嵌套字段丢了」类 bug。
1. setState API 形态
| 调用 | 含义 |
|---|---|
set({ k: v }) | 默认 partial,顶层浅合并 |
set(fn) | fn(prev) 返回 partial 或新 state |
set(obj, true) | replace,整棵 state 替换为 obj |
React 里 set 即 store 的 setState;中间件可能包装 set(如 Immer 传入 draft)。
2. 浅合并(默认)
当前 state 与 partial 顶层 Object.assign:
- 只改
count时,user、items等同层引用保留。 set({ user: { name: 'A' } })会整个替换user键,不会与旧user.age深合并。
嵌套更新选项:
- 手动展开:
set(s => ({ user: { ...s.user, name: 'A' } })) - immer 中间件:
set(s => { s.user.name = 'A' }) - 扁平化 state 结构,减少深层嵌套
3. 完全替换(replace: true)
用于:
- state 形态切换(登出清空)
- partial 非对象(primitive store)
- 明确不要保留旧 key 时
replace 为 true 时不会做 assign,旧 key 若未出现在新对象中会消失。
4. 函数式更新
set(state => ({ count: state.count + 1 })) 基于当前 store 最新值,适合连续多次更新、避免闭包旧值。
与 React setState 函数式更新类似,但发生在 store 层,一次 set 仍触发一轮 listener 通知(React 18 下多次 set 可能被批处理渲染)。
5. 何时不触发订阅
合并/替换后 Object.is(newState, oldState) → 不通知。用于:
- 误写
set({})且无变化 - Immer 返回相同引用(未 mutate)
6. 与 React 渲染的关系
setState同步调用所有 listener。- 已挂载组件通过
useSyncExternalStore在下次渲染读新快照。 - 选择器返回新引用 → 该组件重渲染(见 07-性能优化)。
7. 常见错误对照
| 现象 | 常见原因 |
|---|---|
| 嵌套字段被「抹掉」 | 浅合并 + 只传了子对象一半字段 |
| 更新了 UI 不变 | 选择器引用未变;或 Object.is 判定相等 |
| 异步里读到旧 state | 应用 get() 或函数式 set |
8. 总结
- 默认浅合并,只管顶层 key。
replace: true整表替换。- 函数式 set 对齐最新 store。
- 深层结构优先 Immer 或扁平 state。
实现细节见 02-VanillaStore。