5月28日 01:18
Zustand 中的 set 函数有几种使用方式?
Zustand 的 set 函数有三种使用方式,面试中需要完整回答。
1. 对象式更新——直接传入新状态
javascriptconst useStore = create((set) => ({ count: 0, reset: () => set({ count: 0 }) }));
适用于更新不依赖当前状态值的场景,写法简洁。set 会将传入的对象与当前状态浅合并,未提及的字段保持不变。
2. 函数式更新——基于当前状态计算新值
javascriptconst useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) }));
函数接收当前 state,返回要合并的对象。异步操作中必须用这种写法,否则闭包会捕获过期的 state:
javascript// 错误:count 来自闭包,可能是旧值 const badIncrement = async () => { await delay(1000); set({ count: count + 1 }); }; // 正确:state 始终是最新的 const goodIncrement = async () => { await delay(1000); set((state) => ({ count: state.count + 1 })); };
3. 替换模式——完全替换而非合并
set 的第二个参数 replace 默认为 false(浅合并)。设为 true 时,传入的对象会完全替换当前状态,而非合并:
javascriptconst useStore = create((set) => ({ count: 0, name: 'demo', // 只剩 count,name 被清除 resetAll: () => set({ count: 0 }, true) }));
实际开发中常用于登出时清空整个 store,或切换用户时重置所有字段。
三种方式的对比
| 方式 | 签名 | 状态处理 | 适用场景 |
|---|---|---|---|
| 对象式 | set(partial) | 浅合并 | 不依赖旧值的更新 |
| 函数式 | set((state) => partial) | 浅合并 | 依赖旧值、异步操作 |
| 替换式 | set(partial, true) | 完全替换 | 重置 store |
浅合并的行为细节
浅合并只做第一层合并,嵌套对象需要手动展开:
javascriptconst useStore = create((set) => ({ user: { name: 'Tom', age: 20 }, // 错误:age 丢失 badUpdate: () => set({ user: { name: 'Jerry' } }), // 正确:展开后再覆盖 goodUpdate: () => set((state) => ({ user: { ...state.user, name: 'Jerry' } })) }));
如果嵌套层级深,可以配合 immer 中间件来简化写法。
面试追问:set 为什么默认浅合并而不是深合并?
性能。深合并需要递归遍历整个状态树,对大多数场景来说是不必要的开销。浅合并只需一次 Object.assign 级别的操作,配合 React 的引用比较就能高效判断是否需要重渲染。需要深合并时,开发者自行选择 immer 等工具即可。