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

为何无法在IntervalTimer类的pause()方法中访问timerId属性?

问题:IntervalTimer类pause方法中timerId为null无法清除定时器

我尝试使用IntervalTimer类实现定时器的播放与暂停功能,但pause()方法存在问题:调用该方法时,this.timerId返回null,无法正常清除定时器。而在start()方法中,timerId却能正确获取到对应的定时器ID。以下是我的类实现代码及调用代码:

class IntervalTimer {
  callbackStartTime;
  remaining = 0;
  paused = false;
  timerId = null;
  _callback;
  _delay;

  constructor(callback, delay) {
    this._callback = callback;
    this._delay = delay;
  }

  pause() {
    console.log("this.timerId", this.timerId); //return null
    if (!this.paused) {
      this.clear();
      this.remaining = new Date().getTime() - this.callbackStartTime;
      this.paused = true;
    }
  }

  resume() {
    if (this.paused) {
      if (this.remaining) {
        setTimeout(() => {
          this.run();
          this.paused = false;
          this.start();
        }, this.remaining);
      } else {
        this.paused = false;
        this.start();
      }
    }
  }

  clear() {
    clearInterval(this.timerId);
  }

  start() {
    console.log("this.timerId", this.timerId); // return timerId correctly
    this.clear();
    this.timerId = setInterval(() => {
      this.run();
    }, this._delay);
  }

  run() {
    this.callbackStartTime = new Date().getTime();
    this._callback();
  }
}

export default IntervalTimer;

调用代码:

const interval = new IntervalTimer(() => {
    // 获取剩余数字中的随机数并移除
    let randomIndex = Math.floor(Math.random() * numbersLeft.length);
    let randomNumber = numbersLeft[randomIndex];
    if (randomIndex > -1) numbersLeft.splice(randomIndex, 1);

    // 将选中的数字传入状态
    const tempNList = numberList;
    if (lastNumber) numberList.push(lastNumber);

    setNumberList(tempNList);
    setNumber(randomNumber);

    lastNumber = randomNumber;
  }, BALLTIME);

  const startGame = () => {
    interval.start();
  };

  const pauseGame = () => {
    interval.pause();
  };

问题原因与解决方案

核心问题:实例不唯一(大概率是React环境下的渲染导致)

如果你的代码是在React函数组件中,每次组件重新渲染时,都会重新执行const interval = new IntervalTimer(...)创建全新的实例。这就会出现:

  • 调用startGame()时,操作的是上一次渲染生成的旧实例(该实例已经调用过start,timerId有值)
  • 调用pauseGame()时,操作的是当前渲染生成的新实例(该实例从未调用过start,timerId保持初始值null)

修复方案

用React的useRef钩子存储定时器实例,确保组件生命周期内始终使用同一个实例:

import { useRef, useEffect } from 'react';

// 组件内部
const intervalRef = useRef(null);

// 仅在组件首次渲染时初始化实例
useEffect(() => {
  intervalRef.current = new IntervalTimer(() => {
    // 获取剩余数字中的随机数并移除
    let randomIndex = Math.floor(Math.random() * numbersLeft.length);
    let randomNumber = numbersLeft[randomIndex];
    if (randomIndex > -1) numbersLeft.splice(randomIndex, 1);

    // 修正:创建状态数组的副本,遵循React状态不可变原则
    const tempNList = [...numberList];
    if (lastNumber) tempNList.push(lastNumber);

    setNumberList(tempNList);
    setNumber(randomNumber);

    lastNumber = randomNumber;
  }, BALLTIME);

  // 组件卸载时自动清除定时器,避免内存泄漏
  return () => {
    intervalRef.current?.clear();
  };
}, []); // 空依赖数组确保只执行一次

const startGame = () => {
  intervalRef.current.start();
};

const pauseGame = () => {
  intervalRef.current.pause();
};

额外修正点

你原来的回调中直接修改了状态数组numberList(numberList.push(lastNumber)),这违反了React状态不可变的规则,容易导致状态更新异常。上面的修复代码中已经通过const tempNList = [...numberList]创建数组副本解决了这个问题。

非React环境的排查方向

如果不是在React环境中,检查是否存在多次创建IntervalTimer实例的情况,确保调用start()和pause()的是同一个实例。


内容的提问来源于stack exchange,提问作者dandomin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 17:35:25