React结合setInterval实现秒表:如何修复定时器ID丢失问题?
React秒表定时器ID丢失问题
代码实现
const [elapsedTime, setElapsedTime] = useState(0) // 原本是timer: number | undefined,改成这样也没用,继续看 let timer: { id?: number } = { id: undefined, } const tickSecond = () => setElapsedTime(prevElapsedTime => prevElapsedTime + 1) const resumeClock = () => timer.id = window.setInterval(tickSecond, 1000) const pauseClock = () => { window.clearInterval(timer.id) timer.id = undefined } const toggleClockRun = () => { console.log(`toggleClockRun: timer is`,timer) timer.id ? pauseClock() : resumeClock() } const startClock = () => { setElapsedTime(0) resumeClock() console.log(`startClock: timer is`,timer) }
JSX 渲染部分
<button onClick={startClock}>start</button> <button onClick={toggleClockRun}>pause</button>
预期与实际差异
- 预期行为:点击
start启动秒表;点击pause切换暂停状态 - 实际问题:
startClock中打印出timer is {id: 35}(ID存在),但toggleClockRun中却打印timer is {id: undefined},定时器ID丢失,无法正常执行暂停逻辑;同时JSX中渲染{timer.id}从未显示非空值。
问题根源
你通过let timer声明的变量是组件函数的局部变量,每次组件因状态更新(比如调用setElapsedTime)触发重新渲染时,组件函数会从头执行一遍,timer会被重新初始化为{id: undefined}。因此当你点击toggleClockRun时,访问的是重新初始化后的timer,自然拿不到之前存储的定时器ID。
另外,timer的变化不会触发组件重新渲染,所以即便修改了timer.id,视图也不会同步更新。
解决方案
使用React的useRef钩子存储定时器ID,useRef创建的引用对象在组件整个生命周期内保持唯一,不会随组件重新渲染而重置。
修正后的完整代码:
import { useState, useRef } from 'react'; function Stopwatch() { const [elapsedTime, setElapsedTime] = useState(0); // 用useRef存储定时器ID,初始值为undefined const timerRef = useRef<number | undefined>(undefined); const tickSecond = () => { setElapsedTime(prev => prev + 1); }; const resumeClock = () => { // 避免重复创建定时器 if (!timerRef.current) { timerRef.current = window.setInterval(tickSecond, 1000); } }; const pauseClock = () => { if (timerRef.current) { window.clearInterval(timerRef.current); timerRef.current = undefined; } }; const toggleClockRun = () => { console.log(`toggleClockRun: timer id`, timerRef.current); timerRef.current ? pauseClock() : resumeClock(); }; const startClock = () => { // 启动前先清除旧定时器,防止多个定时器叠加 pauseClock(); setElapsedTime(0); resumeClock(); console.log(`startClock: timer id`, timerRef.current); }; return ( <div> <p>Elapsed Time: {elapsedTime}s</p> <button onClick={startClock}>start</button> <button onClick={toggleClockRun}>pause/resume</button> </div> ); }
核心要点
useRef的.current属性在组件生命周期内持久化,不会因重新渲染被重置- 启动新定时器前先清除旧的,避免多个定时器同时运行导致时间跳变
timerRef.current的修改不会触发组件渲染,适合存储这类不需要驱动视图更新的状态
内容的提问来源于stack exchange,提问作者YakovL
相关产品推荐
相关产品推荐

