5月28日 01:18

Zustand 中的 set 函数有几种使用方式?

Zustand 的 set 函数有三种使用方式,面试中需要完整回答。

1. 对象式更新——直接传入新状态

javascript
const useStore = create((set) => ({ count: 0, reset: () => set({ count: 0 }) }));

适用于更新不依赖当前状态值的场景,写法简洁。set 会将传入的对象与当前状态浅合并,未提及的字段保持不变。

2. 函数式更新——基于当前状态计算新值

javascript
const 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 时,传入的对象会完全替换当前状态,而非合并:

javascript
const useStore = create((set) => ({ count: 0, name: 'demo', // 只剩 count,name 被清除 resetAll: () => set({ count: 0 }, true) }));

实际开发中常用于登出时清空整个 store,或切换用户时重置所有字段。

三种方式的对比

方式签名状态处理适用场景
对象式set(partial)浅合并不依赖旧值的更新
函数式set((state) => partial)浅合并依赖旧值、异步操作
替换式set(partial, true)完全替换重置 store

浅合并的行为细节

浅合并只做第一层合并,嵌套对象需要手动展开:

javascript
const 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 等工具即可。

标签:ReactZustand