为什么 setState 后读到的还是旧值?
先理解原理和示例,再练习完整口述与追问。
回答要点
函数组件每次渲染拿到一份状态快照,setter 请求下一次更新,不会改写当前闭包里的变量。依赖前值更新时使用函数式 updater,而不是把 setter 简单理解为“立即赋值”。
原理拆解
每次函数组件渲染都取得那一次的 state 快照,事件处理函数闭包也关联这份快照。setter 是向 React 提交后续状态更新,并不会改写当前函数里的局部变量。后续渲染会得到新值,当前回调即使又等待了一会儿,也仍然可能引用旧渲染的变量。是否批处理影响何时渲染,但不是快照语义的唯一原因。
示例与推演
function Counter() {
const [count, setCount] = useState(0)
function click() {
setCount(count + 1)
console.log(count) // 本次点击仍是当前渲染快照
}
return <button onClick={click}>{count}</button>
}
若下一次计算依赖前一状态,用 updater;若要在新值提交后同步外部系统,使用合适的 Effect,而不是直接读取 setter 返回值。
易错点
函数组件 useState 不会像类 setState 那样自动浅合并对象。不要用 setTimeout 等一会儿来“修复”闭包,也不要认为 await setter 可以等到新渲染完成。
参考回答可直接练习口述
函数组件一次渲染里的 state 是快照,setter 是提交下一次更新的请求,不会改写当前这次渲染已经拿到的变量。所以 setCount 后立即打印 count 仍是旧快照,即使 await 一个 setter 也不能让当前闭包变成新快照。需要基于最新待处理值更新时用 setCount(c => c + 1),需要观察已提交结果时按语义安排逻辑。
面试官可能继续问追问与完整解答展开收起
追问
setTimeout 里读的是哪次渲染的值?怎么处理基于前一次状态的连续更新?React 官方状态快照
追问回答
setTimeout 回调默认捕获创建它的那次渲染的值。连续依赖前值的更新使用函数式 updater;确实需要异步回调读取最新非渲染信息时可用合适 ref,但不能因此把所有界面状态搬到 ref。