useEffect内更新状态未按预期多次重渲染且定时器失效问题求助
useEffect内更新状态未按预期多次重渲染且定时器失效问题求助
Hey there! Let's break down what's going wrong here and how to fix it. You're hitting a couple of common JavaScript closure pitfalls plus a state management oversight—let's tackle them one by one.
问题根源分析
闭包导致的变量引用问题:
你的newDef变量声明在for循环外部,所有setTimeout回调都会捕获同一个newDef引用。当1秒后定时器触发时,循环已经跑完了150次,newDef早就被更新成最后一次循环的结果,所以状态只会被设置为最后那个值。定时器未错开执行时机:
所有定时器都被设置为1000ms后触发,它们会在同一时间点集中执行,看起来就像是状态瞬间更新,而不是逐步变化。startAnimationFinished不是状态变量:
你直接修改了一个普通变量,但它会在每次组件渲染时重置初始值。如果pathIsActive再次变化,动画可能会意外重复执行。
修复方案
下面是调整后的代码,解决了上述所有问题:
const [definition, setDefinition] = useState(''); // 将startAnimationFinished改为状态变量,确保跨渲染持久化 const [startAnimationFinished, setStartAnimationFinished] = useState(false); useEffect(() => { if (startAnimationFinished) return; if (!pathIsActive) return; // 存储定时器ID,方便后续清理 const timeoutIds = []; for (let i = 0; i < 150; i++) { // 把newDef放到循环内部,让每个迭代都有独立的变量引用 const newDef = `M ${i} ${i} L ${i} ${i}`; // 通过索引错开定时器触发时间(这里用50ms间隔,可根据需求调整) const id = setTimeout(() => { setDefinition(newDef); }, 50 * i); timeoutIds.push(id); } // 使用状态更新器修改startAnimationFinished setStartAnimationFinished(true); // 清理函数:组件卸载或依赖变化时,清除未执行的定时器 return () => { timeoutIds.forEach(id => clearTimeout(id)); }; }, [pathIsActive, startAnimationFinished]); // 把startAnimationFinished加入依赖数组 return ( <> <svg className={'path' + (pathIsActive ? ' active' : '')} xmlns="http://www.w3.org/2000/svg" style={{ border: '1px solid blue' }} viewBox={`${-width / 4} ${-width / 4} ${width} ${width}`} {...props} > <path fill='none' stroke="white" width='1' style={{ filter: "drop-shadow(0px 0px 5px white)" }} d={definition} /> </svg> </> );
额外优化提示
如果想要更流畅的动画效果,可以考虑用requestAnimationFrame替代setTimeout,它会和浏览器的刷新频率同步,减少卡顿感。不过上面的修改已经能解决你当前遇到的核心问题啦。
备注:内容来源于stack exchange,提问作者PRR S2 6-8-6 turbine engine
相关产品推荐
相关产品推荐

