React中setTimeout内多次更新对象状态仅最后一次生效问题求解
问题原因与解决方案
核心问题:闭包捕获了过时的初始state值
你代码中的useEffect依赖项是空数组,因此仅会在组件首次挂载时执行1次,内部定义的3个setTimeout回调,捕获的都是首次渲染阶段的所有变量值:
- 首次渲染时
criteria的初始值是空对象{} - 你当前使用的
setCriteria写法,是直接展开当前作用域内的criteria,而这个被定时器回调捕获的criteria永远是首次渲染时的空对象,不会随着后续state更新而变化
三次更新的实际执行逻辑如下,所以会出现覆盖:
- 1s时执行:
setCriteria({ ...{}, courses: ["calculus", "psychology"] })→ state更新为带courses字段的对象 - 5s时执行:
setCriteria({ ...{}, type: "manual" })→ 还是展开初始空对象,直接覆盖掉之前的courses字段 - 10s时执行:
setCriteria({ ...{}, termLengths: [14, 7] })→ 依旧展开初始空对象,覆盖掉之前的type字段
最终就只剩下最后一次更新的内容。
补充说明:这个问题和React的state批处理机制完全无关。三次更新的触发时机间隔数秒,属于完全独立的事件循环,不存在批处理合并的情况。
修复方案
直接启用你注释掉的函数式更新写法即可。函数式更新的入参prevCriteria永远会拿到当前最新的state值,不会受闭包捕获旧值的影响:
const handleCriteriaChange = (values) => { setCriteria(prevCriteria => ({ ...prevCriteria, ...values })) };
修改后三次更新会正确叠加所有键值对,符合你的预期。
内容的提问来源于stack exchange,提问作者Henok Tesfaye
相关产品推荐
相关产品推荐

