react 中的 setInterval 不会更新 setState

setInterval in react does not update the setState

我想做一个倒数计时器(25 分钟)。这是 App.js 文件

中的代码
import './App.css';
import React, { useState } from 'react';

function App() {
  const [remainingTime, setRemainingTime] = useState(1500);
  const [seconds, setSeconds] = useState(0);
  const [minute, setMinute] = useState(remainingTime/60);

  function timer() {
    setRemainingTime(remainingTime-1);
    let newMinute = remainingTime/60;
    let minuteArray = [...newMinute.toString()];
    setMinute(parseInt(minuteArray.slice(0,2).join("")));
    setSeconds(remainingTime%60);
    console.log(minute);
    console.log(seconds);
  }

  return (
    <div className="App">
      <div className="pomodoro">
        <div className="timer">{minute}:{seconds}</div>
        <div className="button-container">
          <button className="start" onClick={() => setInterval(timer, 1000)}>start</button>
        </div>
      </div>
    </div>
  );
}

export default App;

间隔不更新状态值。分钟值始终为 25,秒值始终为 0。当我不使用 setInterval 而只是像这样使用定时器功能时

<button className="start" onClick={timer}>start</button>

每次我点击开始按钮,值都会改变。任何的想法?我知道我也应该使用 clearInterval 但我不知道把它放在哪里。我应该创建一个包含 setInterval 和 clearInterval 的新函数吗?

在每次重新渲染时,计时器设置为 1500。此处使用闭包,在单击时激活。

function timer() {
    let t = remainingTime;
    setInterval(() => {
      t -= 1;
      setRemainingTime(t);
      let newMinute = t / 60;
      let minuteArray = [...newMinute.toString()];
      setMinute(parseInt(minuteArray.slice(0, 2).join("")));
      setSeconds(t % 60);
      console.log(minute);
      console.log(seconds);
    }, 1000); } 

问题

主要问题是 timer 回调中状态的陈旧外壳。

看来你把它弄得有点复杂了。

解决方案

  1. minuteseconds 被认为是派生状态(来自 remainingTime)所以它不应该也存储在状态中,它很容易从状态中计算出来。
  2. 使用功能状态更新从先前的状态更新 remainingTime 状态,而不是回调排队的渲染周期的状态。
  3. 使用 React 引用来保存间隔计时器引用,因此可以清除间隔。
  4. 使用 useEffect 挂钩 return 清除函数以在组件卸载时清除任何 运行 间隔。

代码:

function App() {
  const timerRef = React.useRef();

  React.useEffect(() => {
    return () => clearInterval(timerRef.current);
  }, []);

  const [remainingTime, setRemainingTime] = React.useState(1500);

  function timer() {
    setRemainingTime((remainingTime) => remainingTime - 1);
  }

  const startTimer = () => {
    clearInterval(timerRef.current);             // clear any running interval
    setRemainingTime(1500);                      // reset state back to 25 minutes
    timerRef.current = setInterval(timer, 1000); // start/restart interval
  };

  const minute = String(Math.floor(remainingTime / 60)).padStart(2, 0);
  const seconds = String(remainingTime % 60).padStart(2, 0);

  return (
    <div className="App">
      <div className="pomodoro">
        <div className="timer">
          {minute}:{seconds}
        </div>
        <div className="button-container">
          <button className="start" onClick={startTimer}>
            start
          </button>
        </div>
      </div>
    </div>
  );
}

演示

因为这可能是一个 follow-up 问题,所以使用另一个 useEffect 来“监听” remainingTime 状态,当时间达到 0 时,清除间隔,重置 remainingTime back 1500 (for display), 并显示番茄钟的任何警报或警报或其他内容 break/schedule.