React useEffect如何清除setInterval?倒计时按钮卸载时清理定时器方法
无限渲染根因
- 你当前把定时器ID存在
timer状态里,且该字段被加入了useEffect的依赖数组 - useEffect每次执行前都会触发上一轮的清理函数,清理函数中执行
setTimer(null)修改了状态,会再次触发useEffect执行,陷入死循环 - 同时原有定时器逻辑存在闭包问题,定时器内读取的
count是创建定时器时的快照,不是最新值,倒计时逻辑本身也存在缺陷
正确实现方案
将定时器ID存在useRef中即可解决,定时器ID不需要触发渲染,用ref存储不会触发重渲染,也不会产生闭包问题:
const CooldownButton = ({ cooldown, classes, counterProps, ...props }) => { const defaultClasses = useStyles(); const [count, setCount] = useState(cooldown); // 用ref存定时器ID,不触发渲染,跨渲染周期保持最新值 const timerRef = useRef(null); useEffect(() => { // 仅当倒计时大于0且没有正在运行的定时器时启动 if (count > 0 && !timerRef.current) { timerRef.current = setInterval(() => { setCount(prevCount => { if (prevCount <= 1) { // 倒计时到0时自动清理定时器 clearInterval(timerRef.current); timerRef.current = null; return 0; } return prevCount - 1; }); }, 1000); } // 清理逻辑:组件卸载、count变化时自动执行 return () => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } }; // 依赖仅需要count,ref不需要加入依赖数组 }, [count]); useUpdateEffect(() => { setCount(cooldown); }, [cooldown]); return ( // ... <Typography size={24} className={clsx(defaultClasses.counter, classes?.counter)} {...counterProps} > {`${new Date(count * 1000) .toISOString() .substr(...(count >= 3600 ? [11, 8] : [14, 5]))}`} </Typography> // ... ); };
方案说明
- 用
useRef存储定时器ID,修改ref值不会触发组件重渲染,从根源避免了清理逻辑触发无限渲染的问题 - 倒计时修改
count时使用setCount的回调函数读取最新值,彻底解决闭包问题 - 清理逻辑会在组件卸载时自动执行,完全覆盖类组件
componentWillUnmount生命周期的清理需求
内容的提问来源于stack exchange,提问作者CSSer
相关产品推荐
相关产品推荐

