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

React中如何通过动态key在useState中向对象数组添加新值?

15秒视频录制网站的Blob捕获问题

我开发了一个15秒视频录制网站,需要每40毫秒(25FPS)将canvas捕获为blob,预期能得到375个blob。

我试过两种定时执行的方案:

// requestAnimationFrame 方案
const loop = async () => {
  sequenceRef.current = setTimeout(() => {
    setSequence((prev) => [
       ...prev,
       new Promise((resolve) => webcam.toBlob(resolve, "image/webp")),
    ]);
    animationFrame.current = requestAnimationFrame(loop);
  }, 40);
};
loop();

// setInterval 方案
sequenceRef.current = setInterval(() => {
  setSequence((prev) => [
    ...prev,
    new Promise((resolve) => webcam.toBlob(resolve, "image/webp")),
  ]);
}, 1000 / FPS);

但不管用requestAnimationFrame还是setInterval,有时能正常捕获到足够数量的blob,有时数量却不足375个——推测是设备性能波动导致的。

于是我换了个思路:创建一个按秒分组的时间映射对象,每个key对应同秒内捕获的blob数组,允许某几秒内丢失部分blob。期望得到的数据格式如下:

timeSequence = {
  "1": ["blob-1s-1", "blob-1s-2", "blob-1s-3"],
  "2": ["blob-2s-1", "blob-2s-2"],
  "3": ["blob-3s-1", "blob-3s-2", "blob-3s-3"],
  ...
}

但用useState实现时遇到了问题:每个key对应的数组始终只保留一个值,无法累加。我的代码如下(不用在意数组里存Promise的问题):

const [sequenceObj, setSequenceObj] = useState<any>({
  "0": [],
  "1": [],
  "2": [],
  "3": [],
  "4": [],
  "5": [],
  "6": [],
  "7": [],
  "8": [],
  "9": [],
  "10": [],
  "11": [],
  "12": [],
  "13": [],
  "14": [],
  "15": [],
});

useEffect(() => {
  const loop = async () => {
    setTimeout(() => {
      setSequenceObj((prev: any) => ({
        ...prev,
        [`${currentTime}`]: [
          ...sequenceObj[`${currentTime}`],
          new Promise((resolve) => webcam.toBlob(resolve, "image/webp")),
        ],
      }));
      animationFrame.current = requestAnimationFrame(loop);
    }, 1000 / FPS);
  };

  loop();
}, [currentTime])

问题原因与修复方案

问题出在状态更新时引用了旧的sequenceObj,而非最新的状态快照prev。useState的更新是异步的,组件渲染时sequenceObj可能还是初始值或之前的旧状态,每次更新都会基于这个旧数组重新创建,导致新元素无法累加,最终每个key下只有最后一次添加的元素。

修正后的代码应该在更新函数里使用prev来获取当前最新的数组:

useEffect(() => {
  const loop = () => {
    setTimeout(() => {
      setSequenceObj((prev: any) => {
        const currentKey = `${currentTime}`;
        return {
          ...prev,
          [currentKey]: [
            ...prev[currentKey],
            new Promise((resolve) => webcam.toBlob(resolve, "image/webp")),
          ],
        };
      });
      animationFrame.current = requestAnimationFrame(loop);
    }, 1000 / FPS);
  };

  loop();

  // 清理定时器和动画帧,防止内存泄漏
  return () => {
    clearTimeout(sequenceRef.current);
    cancelAnimationFrame(animationFrame.current);
  };
}, [currentTime])

另外补充两点:

  1. 原代码里的async是多余的,setTimeout和requestAnimationFrame不需要异步函数的返回值,去掉可避免不必要的Promise创建。
  2. 必须在useEffect的清理函数中清除定时器和动画帧,防止组件卸载后仍继续执行,造成内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:10:29