You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在setTimeout回调中更新React数组状态的问题及原理解惑

为什么React状态更新在setTimeout中用直接赋值只保留最后一个元素?

问题场景回顾

你最初的代码写法如下:

function Painter({tempArr}) {
  const [arr, setArr] = useState([]);

  useEffect(() => {
    tempArr.map((item, index) => {
      setTimeout(() => {
        setArr([...arr, item]);
      }, 2000 * index)
    })
  }, [tempArr])
}

执行后发现arr最终仅包含一个元素,不符合预期。

改用函数式更新后问题解决:

function Painter({tempArr}) {
  const [arr, setArr] = useState([]);

  useEffect(() => {
    tempArr.map((item, index) => {
      setTimeout(() => {
        setArr(prev => [...prev, item]); // using the updater function
      }, 2000 * index)
    })
  }, [tempArr])
}

第一种写法失效的原因:闭包捕获的是旧状态

useEffect在tempArr变化时执行一次,内部的map会为每个item创建一个setTimeout。每个定时器的回调函数会捕获当前useEffect执行时的arr值——也就是初始的空数组[]。

当定时器依次触发时,每个回调里的setArr([...arr, item])都是基于这个固定的空数组去添加当前item,相当于每次都把状态重置为[当前item]。后面的定时器触发时会覆盖之前的状态,最终arr里只会留下最后一个被处理的元素。

第二种写法生效的原理:函数式更新获取最新状态

使用setArr(prev => [...prev, item])这种函数式更新时,你传给setArr的是一个回调函数。React会在执行这个回调时,自动传入当前最新的状态值prev,而不是依赖闭包捕获的旧值。

不管定时器延迟多久触发,回调里的prev始终是arr的最新状态:

  • 第一个定时器触发时,prev是空数组,添加第一个item后arr变为[item1];
  • 第二个定时器触发时,prev是[item1],添加第二个item后变为[item1, item2];
  • 以此类推,最终arr会包含tempArr的全部元素。

内容的提问来源于stack exchange,提问作者nearworld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:30:43