React应用中setInterval失效:倒计时时器无法正常运行
问题原因分析
- 定时器引用丢失:
timerInt是组件内的普通变量,组件每次状态更新重新渲染时,该变量会被重置,导致无法持续控制定时器。 - 闭包捕获旧状态:
timerF函数读取的sec和min是组件首次渲染时的闭包值,不会随状态更新同步,后续判断逻辑一直基于旧值执行,导致倒计时失效。 - 状态更新顺序错误:无论什么情况最后都执行
setSec(prev=>prev-1),会覆盖sec===0时设置的setSec(59),逻辑冲突。 - 初始化逻辑遗漏:点击开始按钮后未将秒数初始化为59,不符合需求。
修复后的代码
import { useState, useRef, useEffect } from 'react'; function CountdownTimer() { const [min, setMin] = useState(10); const [sec, setSec] = useState(0); const [disabledBtn, setDisabledBtn] = useState(false); // 用useRef保存定时器引用,避免组件重渲染丢失 const timerInt = useRef(null); const timer = () => { // 清除已有定时器,防止重复点击创建多个实例 if (timerInt.current) clearInterval(timerInt.current); timerInt.current = setInterval(() => { setMin(prevMin => { return setSec(prevSec => { // 判断倒计时是否结束 if (prevSec <= 0 && prevMin <= 0) { clearInterval(timerInt.current); setDisabledBtn(false); return 0; } // 秒数为0时重置为59,触发分钟减1 if (prevSec === 0) { return 59; } // 正常秒数递减 return prevSec - 1; }) || (prevSec === 0 && prevMin > 0 ? prevMin - 1 : prevMin); }); }, 1000); }; const handleStart = (time) => { // 初始化分钟和秒数,符合点击开始后秒数为59的需求 setMin(time); setSec(59); setDisabledBtn(true); timer(); }; // 组件卸载时清除定时器,防止内存泄漏 useEffect(() => { return () => { if (timerInt.current) clearInterval(timerInt.current); }; }, []); return ( <div> <div className="time"> <div className="time-min">{min}</div> <span>:</span> <div className="time-sec">{sec < 10 ? `0${sec}` : sec}</div> </div> <button className="start-btn active-btn" type="button" disabled={disabledBtn} onClick={() => handleStart(10)} > START </button> </div> ); } export default CountdownTimer;
关键修复点说明
- 用
useRef存储定时器引用,确保组件重渲染时不丢失对定时器的控制。 - 采用状态的函数式更新,确保每次状态修改都基于最新值。
- 调整逻辑顺序,先判断倒计时是否结束,再处理秒数和分钟的更新,避免状态覆盖。
- 点击开始按钮时直接初始化秒数为59,匹配需求。
- 添加组件卸载时的定时器清理逻辑,防止内存泄漏。
- 优化按钮
disabled属性写法,直接使用布尔值即可。
内容的提问来源于stack exchange,提问作者wch
相关产品推荐
相关产品推荐

