如何在React中暂停一个setInterval倒计时计时器?

3
我正在尝试构建一个带有暂停选项的番茄钟计时器。其中包括一个模拟时钟和一个数字计时器。我的问题在于数字计时器 - 我可以通过清除间隔来暂停它,但不知道如何在不重新开始(使用新的 setInterval)的情况下恢复它。
这是项目的codesandbox
以下是 DigitalClock 组件的相关部分:
const timer = () => {
    const now = Date.now()
    const then = now + mode.duration * 60 * 1000
    countdown = setInterval(() => { // That's how I resume it (with a re-render)
        const secondsLeft = Math.round((then - Date.now()) / 1000)
        if(secondsLeft < 0 || pause) {
            clearInterval(countdown) // That's how I pause it (by clearing the interval)
            return;
        }
        displayTimeLeft(secondsLeft)
    }, 1000)
}
4个回答

2
可能的解决方案-当暂停时不清除间隔,只是在tick上不更新secondsLeft。
另外,secondsLeft可以是整数,它不必与实际时间相关。

// global variables
var pause = false;
var elapsed, secondsLeft = 60;
const timer = () => {
  // setInterval for every second
  countdown = setInterval(() => {
    // if allowed time is used up, clear interval
    if (secondsLeft < 0) {
      clearInterval(countdown)
      return;
    }
    // if paused, record elapsed time and return
    if (pause === true) {
      elapsed = secondsLeft;
      return;
    }
    // decrement seconds left
    secondsLeft--;
    displayTimeLeft(secondsLeft)
  }, 1000)
}
timer();
const displayTimeLeft = (seconds) => {
  document.getElementById("time").textContent = seconds;
}
document.getElementById("pause").addEventListener("click", (evt) => {
  pause = !pause;
  evt.target.textContent = pause ? "resume" : "pause";
  if (pause === false) {
    secondsLeft = elapsed;
  }
});
<div id="time"></div>
<button id="pause">pause</button>


2
我认为可以使用布尔值来暂停计时器,而不是清除间隔。因此,假设您在顶层还有一个布尔值来跟踪暂停状态。 let paused = false; 您应该考虑查找计时器是否未暂停,然后在内部执行数学运算。
countdown = setInterval(() => { // That's how I resume it (with a re-render)
    if(!paused) {
        const secondsLeft = Math.round((then - Date.now()) / 1000)
        if(secondsLeft < 0 || pause) {
            clearInterval(countdown) // That's how I pause it (by clearing the interval)
            return;
        }
        displayTimeLeft(secondsLeft)
    }
}, 1000)

唯一剩下的就是在某人单击“暂停”按钮时将此暂停布尔值切换为true / false。

我对React不太了解,但如果我执行此任务,那么这将是我的选择 :)


1

使用 React,计时器应该在 useEffect hook 中(假设您正在使用函数组件)。useInterval 将被放置在其中,并通过 useEffect hook 自然运行,通过更新显示来更新。将清晰度间隔放置在 useEffect 返回语句中,因此当计时器过期时,间隔将自动清除。

然后,使用暂停作为状态变量,使用按钮管理计时器。


const [seconds, setSeconds] = useState(30);
const [pause, setPause] = useState(false);

useEffect(() => {
  const interval = setInterval(() => {
    if(!pause) { //I used '!paused' because I set pause initially to false. 
      if (seconds > 0) {
        setSeconds(seconds - 1);
      }
    }
  }, 1000);
  return () => clearInterval(interval);
});

const handlePauseToggle = () => {
  setPause(!pause);
}

添加一个按钮进行点击,你的暂停功能就设置好了。

*** 附注,请随意忽略 *** 看起来你已经有了一种显示时间的方法,但我认为如果你使用花括号中的“秒”状态变量来显示计时器,而不是创建一个函数,会更容易(见下文)。


<div>
  <p>0:{seconds >= 10 ? {seconds} : `0${seconds}`}</p>
</div>

这将在单行中正确显示计时器。分钟很容易添加等等。

只是一个让你的代码更简单的想法。


1

使用自定义钩子

按照以下步骤进行:

  • 创建一个新的状态变量,在恢复时存储计数器值
  • 在启动时检查是否有恢复值,如果有则使用它,否则使用初始计数器

这是完整的自定义钩子:

const useCountdown = ({ initialCounter, callback }) => {
  const _initialCounter = initialCounter ?? DEFAULT_TIME_IN_SECONDS,
    [resume, setResume] = useState(0),
    [counter, setCounter] = useState(_initialCounter),
    initial = useRef(_initialCounter),
    intervalRef = useRef(null),
    [isPause, setIsPause] = useState(false),
    isStopBtnDisabled = counter === 0,
    isPauseBtnDisabled = isPause || counter === 0,
    isResumeBtnDisabled = !isPause;

  const stopCounter = useCallback(() => {
    clearInterval(intervalRef.current);
    setCounter(0);
    setIsPause(false);
  }, []);

  const startCounter = useCallback(
    (seconds = initial.current) => {
      intervalRef.current = setInterval(() => {
        const newCounter = seconds--;
        if (newCounter >= 0) {
          setCounter(newCounter);
          callback && callback(newCounter);
        } else {
          stopCounter();
        }
      }, 1000);
    },
    [stopCounter]
  );

  const pauseCounter = () => {
    setResume(counter);
    setIsPause(true);
    clearInterval(intervalRef.current);
  };

  const resumeCounter = () => {
    startCounter(resume - 1);
    setResume(0);
    setIsPause(false);
  };

  const resetCounter = useCallback(() => {
    if (intervalRef.current) {
      stopCounter();
    }
    setCounter(initial.current);
    startCounter(initial.current - 1);
  }, [startCounter, stopCounter]);

  useEffect(() => {
    resetCounter();
  }, [resetCounter]);

  useEffect(() => {
    return () => {
      stopCounter();
    };
  }, [stopCounter]);

  return [
    counter,
    resetCounter,
    stopCounter,
    pauseCounter,
    resumeCounter,
    isStopBtnDisabled,
    isPauseBtnDisabled,
    isResumeBtnDisabled,
  ];
};

这里是在 CodePen 上使用它的示例: React useCountdown


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接