为何无法在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
相关产品推荐
相关产品推荐

