繁体   English   中英

使用递归 setTimeout 启动和停止计时器

[英]Start and Stop a Timer with Recursive setTimeout

我正在尝试使用递归setTimeout启动和停止计时器,因为setInterval不符合我的需要。 当用户按下开始时,计时器开始计时。

1...2...3...4..5...1...2...3..4...5...1..2...3..4。 ..5...

当用户按下停止时,它应该停止这个定时器循环。 如果我再次按下开始,更改按钮(流变量)的 state,计时器应该再次做同样的事情(1..2..3..4..5..1..2...3。 .4...5..)

我认为这样做的方法就是这样。


const ref = useRef(null);
useEffect(() => {
  if (!stream) {
    ref.current=setTimeout(repeatingFunc, 1000);

  } else {
    clearTimeout(ref.current);
  }
}, [stream]);

function repeatingFunc() {
  console.log("It's been 5 seconds. Execute the function again.");
  setTimeout(repeatingFunc, 5000);
}

我究竟做错了什么?

计时器不会停止。 即使我按下停止,它也会继续运行! (更改 stream state 值!)

您可以像这样定义自定义计时器:

function CustomTimer(func) {
    this.id = -1;
    this.func = func;

    this.start = function (interval) {
        this.stop();

        if (this.func) {
            var t = this;
            this.id = setTimeout(function () { t.func.call(t) }, interval || 1000);
        }
    }

    this.stop =  function () {
        if (this.id >= 0) {
            clearTimeout(this.id);
            this.id = -1;
        }
    }
}

并使用它:

var myTimer = new CustomTimer(function () {
    console.log("It's been 5 seconds. Execute the function again.");
    this.start(5000);
});

myTimer.start(5000);

您可以将 function 更改为随时执行,也可以更改间隔。 例如:在 1 秒内运行启动事件,然后每 5 秒运行 5 次其他操作:

var secondFunc = function () {
    console.log("It's been 5 seconds. Execute the function again.");

    if (++this.count < 5)
        this.start(5000);
}

var myTimer = new CustomTimer(function () {
    console.log("First event at 1 second");

    this.count = 0;
    this.func = secondFunc;
    this.start(5000);
});

myTimer.start(1000);

计时器不停止的原因是因为您只存储了初始setTimeout调用的超时 ID。 repeatingFunc被调用时,另一个setTimeout回调被注册,它有一个新的超时 ID。

因此,当您尝试使用clearTimeout(ref.current)清除超时时,您传递的是一个过时的超时 ID。

由于之前的setTimeout注册已经被调用,你不再需要存储之前的 ID,你可以简单地用新的 ID 替换它。

const ref = useRef(null);

useEffect(() => {
  if (!stream) {
    ref.current = setTimeout(repeatingFunc, 1000);
  } else {
    clearTimeout(ref.current);
  }
}, [stream]);

function repeatingFunc() {
  console.log("It's been 5 seconds. Execute the function again.");
  ref.current = setTimeout(repeatingFunc, 5000);
  // ^^^^^^^^^^^^ update timeout ID after registering a new timeout
}

请注意, repeatingFunc的 scope 固定在您最初调用setTimeout(repeatingFunc, 1000)的时间点,这意味着状态和属性等内容可能包含过时的值。 这可能会或可能不会成为问题,具体取决于您的上下文。


您可能还想添加一个useEffect cleanup function 来清除组件卸载的超时。 否则,即使不再安装组件,您的循环也会继续。

useEffect(() => {
  if (!stream) {
    ref.current = setTimeout(repeatingFunc, 1000);
  }

  return () => clearTimeout(ref.current);
}, [stream]);

假设stream是 boolean ( true / false ),只要stream的值发生变化,或者在组件卸载时,上述内容就会清除当前超时。

streamtrue变为false时,将调用清理 function 并清除超时。

streamfalse变为true时,清理 function 也会被调用,但会导致无操作,因为当前的ref.current不再被注册。 然后设置新的超时。

将无效 ID 传递给clearTimeout()静默不执行任何操作; 没有异常被抛出。

请参阅: MDN - clearTimeout()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM