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])
另外补充两点:
- 原代码里的
async是多余的,setTimeout和requestAnimationFrame不需要异步函数的返回值,去掉可避免不必要的Promise创建。 - 必须在
useEffect的清理函数中清除定时器和动画帧,防止组件卸载后仍继续执行,造成内存泄漏。
内容的提问来源于stack exchange,提问作者xbique
相关产品推荐
相关产品推荐

