You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 18:23:11