React组件中for循环内setTimeout导致计数器无限递增问题
解决React计数器无限递增的问题
问题根源分析
你的代码存在两个核心问题,导致计数器停不下来:
- 闭包陷阱:for循环里的每个
setTimeout捕获的是组件初次渲染时的currentNumber值(固定为0),所以所有定时器触发时都会执行setCurrentNumber(0 + 1),最终只是多次把值设为1,而非预期的依次递增。 - 重复创建定时器:组件每次状态更新都会重新执行整个函数,包括for循环。这意味着
currentNumber每变化一次,就会新建number个定时器,导致定时器越积越多,计数器持续触发更新。
修复方案(推荐)
使用useEffect控制定时器的创建与清理,结合函数式状态更新获取最新值:
import { useState, useEffect } from 'react'; const Counter = ({ number }) => { const [currentNumber, setCurrentNumber] = useState(0); useEffect(() => { // 初始值已达目标,直接返回 if (currentNumber >= number) return; const intervalId = setInterval(() => { setCurrentNumber(prev => { // 达到目标值时清除定时器 if (prev >= number - 1) { clearInterval(intervalId); return number; } return prev + 1; }); }, 2000); // 组件卸载或参数变化时清理定时器,防止内存泄漏 return () => clearInterval(intervalId); }, [number, currentNumber]); return <span>{currentNumber}</span>; }; export default Counter;
方案说明
- 用
useEffect包裹定时器逻辑,依赖项设为number和currentNumber,确保只有当这两个值变化时才重新执行。 - 使用
setCurrentNumber(prev => ...)的函数式更新,每次都能获取到最新的状态值,彻底避开闭包问题。 - 在达到目标值时主动清除定时器,同时在组件卸载或依赖项变化时清理定时器,避免内存泄漏。
另一种可选方案(递归式setTimeout)
如果需要更精准的间隔控制(避免setInterval的累积延迟),可以用递归创建定时器:
import { useState, useEffect } from 'react'; const Counter = ({ number }) => { const [currentNumber, setCurrentNumber] = useState(0); useEffect(() => { let timeoutId; const increment = () => { if (currentNumber >= number) return; setCurrentNumber(prev => prev + 1); timeoutId = setTimeout(increment, 2000); }; timeoutId = setTimeout(increment, 2000); return () => clearTimeout(timeoutId); }, [number, currentNumber]); return <span>{currentNumber}</span>; }; export default Counter;
内容的提问来源于stack exchange,提问作者R. Shoemaker
相关产品推荐
相关产品推荐

