Sunday面试指南

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 还会按规则批处理更新,因此不能把它简化成固定延迟的异步赋值。需要基于旧状态计算时使用纯更新函数;需要在事件里立刻使用某个计算值,就自己明确算出来,而不是等当前闭包变量发生变化。

同一渲染快照提交三次设为1,而三次增量更新在队列中累计到3

知识点详解:状态快照和更新队列,分别解决什么问题

同一个点击函数,看见的是同一张快照

假设计数器本轮显示 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 的合并规则不同。

公司面试真题

这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。

浏览公司面试真题 →
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历