React 的 setState 为什么不能立即读到新值?函数式更新解决什么问题?
🧑💻 面试官:为什么调用 setState 后,马上读到的还是旧值?
🙋♂️ 我:因为 React 的状态更新是异步的,等更新完就能读到新值。
🧑💻 面试官:用 useState 时,同一个点击里连续三次 setCount(count + 1),从 0 会变成 3 吗?
🙋♂️ 我:可能只变成 1,所以要用函数式更新。
🧑💻 面试官:那函数式更新后,在这个点击函数里打印 count 就能看到 3 吗?
先理解本轮渲染的状态快照,再看更新队列;函数式更新不是把当前变量立即改掉。
面试速答(60 秒版)
以 useState 为例,每次渲染拿到的是这一轮的状态快照。调用 setter 是提交下一次更新,不会把当前事件函数里的 count 变量改成新值。
所以连续三次 setCount(count + 1),可能都提交同一个计算结果。用 setCount(c => c + 1),则是把更新函数放进队列,后一个更新接着前一个结果计算。
React 还会按规则批处理更新,因此不能把它简化成固定延迟的异步赋值。需要基于旧状态计算时使用纯更新函数;需要在事件里立刻使用某个计算值,就自己明确算出来,而不是等当前闭包变量发生变化。

知识点详解:状态快照和更新队列,分别解决什么问题
同一个点击函数,看见的是同一张快照
假设计数器本轮显示 0,按钮点击函数也是基于这一轮渲染产生的。点击时执行 setCount(count + 1),它提交的是“下一次状态设为 1”。
当前函数里的 count 仍是 0。继续执行第二次和第三次同样的表达式,提交的也都是 1,不会自动变成“在上一次提交结果上再加一”。
延时回调也可能保留创建它时的状态快照。不是简单加个 setTimeout,就能让旧闭包里的 count 自动变新。要读后续状态,应使用适合需求的状态更新、Effect 或 ref 设计,而不是混淆它们。
函数式更新,把计算交给队列
如果希望连续增加三次,提交三次纯更新函数。React 处理这批队列时,先把原值 0 交给第一个更新,得到 1;再得到 2,最后得到 3。
这是 React TypeScript 组件示例,需要安装 React 及对应类型,未启用 React Compiler。Python 没有这套浏览器组件 API,不提供机械翻译。
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => {
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
console.log(count); // 本次事件仍看到当前渲染的快照
}}>{count}</button>;
}
注意,函数式更新只是改变队列中的计算方式,console.log(count) 仍不会在这里读到新一轮的 3。
更新函数要保持纯粹。不要把发请求或写日志业务塞进去,再指望它只执行一次;开发环境 Strict Mode 可能重复调用更新函数来帮助发现问题。
批处理和快照,不能用一句异步概括
批处理表示 React 可以把多次更新安排到一起,减少中间渲染。但当前函数使用本轮快照,是另一个层面的问题。
现代 React 根的自动批处理还覆盖一些 Promise、定时器等情形,具体行为要结合 React 版本和根 API。面试里说“React 合成事件异步、原生事件同步”容易把旧版本经验误当成永恒规则。
类组件的 setState 和 useState 也不能完全混着讲。类组件对象更新会进行相应合并;useState 的 setter 替换的是这个状态值,更新对象时通常要自己保留所需字段。
需要新结果时,先说明想做什么
假设点击后还要把新数量提交给服务端。可以在事件中根据明确输入计算要提交的值,再更新状态和发送请求;如果存在多个并发更新来源,则需要更完整的事件或状态模型,不能靠一个闭包中的 count 保证业务一致。
如果只是要在界面提交更新后同步某个外部系统,可以按需要使用 Effect。Effect 不是通用的“setState 完成回调”,也不应该承接本来就属于点击事件的所有业务。
验证时分别看当前事件日志、下一轮界面和连续点击结果,把这三个时刻分开,问题就容易解释了。
本题机制参考:React:State as a Snapshot、React:Queueing State Updates。
面试官继续追问
函数式更新一定比直接传值好?
不是。设置一个明确的新值时,直接传值很合适;基于待处理旧状态计算时,更新函数更稳妥。
flushSync 能把当前闭包变量变成新值吗?
不能。它能在特定场景强制同步刷新相应 DOM 更新,但当前函数里的快照变量不会因此变成下一轮值,而且应谨慎使用。
为什么对象状态更新后其他字段丢了?
useState 不会自动浅合并新旧对象。需要保留字段时,应返回包含这些字段的新对象;还要避免直接修改旧对象。
面试速记卡
- 本轮状态:一次渲染提供一张快照。
- setter:提交更新,不修改当前闭包里的变量。
- 函数式更新:后一个计算接着队列中的前一个结果。
- 更新函数:保持纯粹,不放业务副作用。
- 版本边界:批处理看根 API,类 setState 与 useState 的合并规则不同。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →