React定时器调用clearInterval()后重启而非暂停,求排查
问题描述
我希望通过切换按钮实现定时器的启停,同时添加重置按钮,但每次点击停止后定时器都会自动重启。请问我的代码哪里出现了问题?
相关代码
timer.js
import React, { useState, useImperativeHandle, forwardRef } from 'react' const Timer = forwardRef((props, ref) => { // 注:这里需要用ref来追踪setInterval,以便在需要时停止它 let interval = null; const [total, setTotal] = useState(3540); useImperativeHandle(ref, () => ({ startTimerWrapper() { interval = setInterval(() => { setTotal(total => total + 1) },1000) console.log(interval); return () => clearInterval(this.interval) }, closeTimerWRapper(){ clearInterval(interval); console.log("cleared " + this.interval); setTotal(0); } })); return ( <> <h1 > <span>{("0" + Math.floor(total / 3600)).slice(-2)}:</span> <span>{("0" + Math.floor((total % 3600) / 60)).slice(-2)}:</span> <span>{("0" + ~~(total % 60)).slice(-2)}</span> </h1> </> ) }) export default Timer;
App.js
function App() { const [started, setStarted] = React.useState(false); const timerRef = useRef(null); const onToggle = () => { let id = null; setStarted(!started); if(!started){ id = timerRef.current.startTimerWrapper(); console.log("1"); } else { timerRef.current.closeTimerWRapper(); console.log("0 " + id); } } return ( <div className="App" style={{ padding: 40 }} > <Card className="main" style={style.main}> <CardContent> <Stack direction="row" justifyContent="center" spacing={2} > <Item> <StyledToggleButton value="false" selected={started} onChange={() => { onToggle(); }} > <PlayCircleOutlineIcon sx={{display: started ? 'none' : 'block'}}/> <StopIcon sx={{display: started ? 'block' : 'none' }}/> </StyledToggleButton> </Item> </Stack> <div justifycontent="center" display="flex"><Timer ref={timerRef} ></Timer></div> </CardContent> </Card> </div> ); } export default App;
问题原因与修复方案
核心问题
- 定时器ID未持久化存储:Timer组件里的
interval是普通变量,组件每次因setTotal更新状态而重渲染时,这个变量会被重置为null。调用closeTimerWRapper时,拿到的是新的null值,无法清除之前启动的定时器,导致旧定时器一直运行,看起来像是“停止后自动重启”。 - 清理函数无效:
startTimerWrapper里返回的清理函数没有被触发,且this.interval指向错误,无法正确清除定时器。
修复步骤
1. 用useRef存储定时器ID
把普通的let interval = null替换为useRef,确保定时器ID在组件生命周期内保持不变:
// 在Timer组件导入useRef import React, { useState, useImperativeHandle, forwardRef, useRef } from 'react' // 替换原来的let interval = null; const intervalRef = useRef(null);
2. 修改启停与重置方法
更新startTimerWrapper、closeTimerWRapper,新增resetTimer方法,使用intervalRef.current操作定时器:
useImperativeHandle(ref, () => ({ startTimerWrapper() { // 启动前先清除旧定时器,避免重复创建 if (intervalRef.current) { clearInterval(intervalRef.current); } intervalRef.current = setInterval(() => { setTotal(total => total + 1); }, 1000); }, closeTimerWRapper(){ if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; // 清除后重置ref setTotal(0); } }, resetTimer() { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } setTotal(0); } }));
3. 优化App组件的切换逻辑
setStarted是异步操作,用函数式更新确保拿到最新状态:
const onToggle = () => { setStarted(prevStarted => { if (!prevStarted) { timerRef.current.startTimerWrapper(); } else { timerRef.current.closeTimerWRapper(); } return !prevStarted; }); }
4. 添加重置按钮
在App组件的Stack中加入重置按钮:
<Item> <Button onClick={() => timerRef.current.resetTimer()}> 重置 </Button> </Item>
完整修复后的Timer组件
import React, { useState, useImperativeHandle, forwardRef, useRef } from 'react' const Timer = forwardRef((props, ref) => { const intervalRef = useRef(null); const [total, setTotal] = useState(3540); useImperativeHandle(ref, () => ({ startTimerWrapper() { if (intervalRef.current) { clearInterval(intervalRef.current); } intervalRef.current = setInterval(() => { setTotal(total => total + 1); }, 1000); }, closeTimerWRapper(){ if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; setTotal(0); } }, resetTimer() { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } setTotal(0); } })); return ( <> <h1 > <span>{("0" + Math.floor(total / 3600)).slice(-2)}:</span> <span>{("0" + Math.floor((total % 3600) / 60)).slice(-2)}:</span> <span>{("0" + ~~(total % 60)).slice(-2)}</span> </h1> </> ) }) export default Timer;
内容的提问来源于stack exchange,提问作者Samra
相关产品推荐
相关产品推荐

