1

在 Reason 中,当满足某些条件时,让间隔自动清除的最优雅的方式是什么?在 JavaScript 中,我可以这样做:

var myInterval = setInterval(function () {
    // do some stuff
    if (fancyCondition) {
        clearInterval(myInterval);
    }
}, 1000);

在 Reason 中,到目前为止我想出的最好的是:

let intervalIdRef = ref(None);
let clearInterval = () =>
    switch (intervalIdRef^) {
    | Some(intervalId) => Js.Global.clearInterval(intervalId)
    | None => ()
    };
let intervalId = Js.Global.setInterval(() => {
    /* do some stuff */
    fancyCondition ? clearInterval() : ();
}, 1000);
intervalIdRef := Some(intervalId);

有没有办法避免使用 a ref

4

1 回答 1

3

setInterval/clearInterval本质上是可变的,但即使它不是你fancyCondition的,所以在ref这里删除它不会给你带来很多好处。我认为即使ref它可以通过封装进行改进,并且稍微取决于您fancyCondition,我们应该能够通过使用setTimeout而不是setInterval/以纯粹的功能方式获得相同的行为clearInterval

首先,让我们通过添加一个计数器、打印计数、然后在达到计数 5 时清除间隔来使您的示例具体化,这样我们就可以使用一些东西了:

let intervalIdRef = ref(None);
let count = ref(0);

let clearInterval = () =>
  switch (intervalIdRef^) {
    | Some(intervalId) => Js.Global.clearInterval(intervalId)
    | None => ()
  };

let intervalId = Js.Global.setInterval(() => {
  if (count^ < 5) {
    Js.log2("tick", count^);
    count := count^ + 1;
  } else {
    Js.log("abort!");
    clearInterval();
  }
}, 200);

intervalIdRef := Some(intervalId);

我认为我们应该做的第一件事是通过将定时器状态/句柄包装在一个函数中并传递clearInterval给回调来封装它,而不是将它作为一个单独的函数,我们可能会在不知道它是否真的做任何事情的情况下多次调用它:

let setInterval = (timeout, action) => {
  let intervalIdRef = ref(None);
  let clear = () =>
    switch (intervalIdRef^) {
      | Some(intervalId) => Js.Global.clearInterval(intervalId)
      | None => ()
    };

  let intervalId = Js.Global.setInterval(() => action(~clear), timeout);
  intervalIdRef := Some(intervalId);
};

let count = ref(0);
setInterval(200, (~clear) => {
  if (count^ < 5) {
    Js.log2("tick", count^);
    count := count^ + 1;
  } else {
    Js.log("abort!");
    clear();
  }
});

我们现在已经摆脱了全局计时器句柄,我认为它回答了您最初的问题,但我们仍然坚持count作为全局状态。所以让我们也摆脱它:

let rec setTimeout = (timeout, action, state) => {
  let continue = setTimeout(timeout, action);
  let _:Js.Global.timeoutId =
    Js.Global.setTimeout(() => action(~continue, state), timeout)
};

setTimeout(200, (~continue, count) => {
  if (count < 5) {
    Js.log2("tick", count);
    continue(count + 1);
  } else {
    Js.log("abort!");
  }
}, 0);

在这里,我们将问题颠倒了一点。我们没有使用setIntervalandclearInterval并将clear函数传递到我们的回调中,而是传递给它一个continue在我们想要继续而不是在我们想要退出时调用的函数。这允许我们向前传递状态,并ref通过使用递归而不使用突变和 s 来更改状态。而且它用更少的代码来做到这一点。我认为这很优雅,如果不是你所要求的:)

于 2018-05-19T17:58:23.140 回答