首页 > 解决方案 > setInterval + React 钩子导致组件内的多次更新

问题描述

我正在构建一个秒表 UI,以秒为单位显示时间。单击按钮,计时器将开始向上计数,并在再次单击时停止。用户应该能够再次启动它。

我遇到的问题是我可以setInterval正常工作,但是一旦我包含setTime钩子,组件就会更新以在 UI 中呈现时间,但setInterval实例被多次调用。这会导致奇怪的渲染行为。

const Timer = () => {
    const [time, setTime] = useState(0)
    let timer

    const startStopTimer = () => {
        if (!timer) timer = setInterval(() => setTime(time++), 1000)
        else {
           clearInterval(timer)
           timer = null
        }
    }

    return (
            <div>
               <p>Time: {time} seconds</p>
               <Button 
                   onClick={() => {
                      startStopTimer()
                   }
               > Start/Stop </Button>
            </div>
           )
}

示例行为是:

  1. 用户单击开始/停止
  2. 定时器从 0 开始向上计数
  3. 用户单击开始/停止
  4. 定时器立即停止
  5. 用户单击开始/停止
  6. 计时器从停止的地方继续

标签: javascriptreactjsreact-hookssetinterval

解决方案


这是 React 钩子中陈旧闭包的经典示例,在您的 setInterval 值time在调用后不会改变setTime。更改您的代码:

setInterval(() => setTime(currentTime => currentTime + 1), 1000).

setTime就像setState有类组件一样,它也接受一个回调函数,该函数将当前值作为第一个参数

此外,该timer变量在您的代码中是无用的,因为在每次重新渲染时它将是未定义的,并且您将无法访问 . 的返回值setInterval,因此它将重新初始化setInterval. 为了处理这种使用useRef,您可以存储 in 的返回值,setInterval.current后续重新渲染后您可以使用它,因此不再重新初始化 setInterval 并且您也可以使用clearInterval

解决方案:

const {useState, useRef} = React;
const {render} = ReactDOM;

const Timer = () => {
  const [time, setTime] = useState(0);
  const timer = useRef(null);
  const startStopTimer = () => {
    if (!timer.current) {
      timer.current = setInterval(() => setTime(currentTime => currentTime + 1), 1000);
    } else {
      clearInterval(timer.current);
      timer.current = null;
    }
  };

  return (
    <div>
      <p>Time: {time} seconds</p>
      <button
        onClick={startStopTimer}
      >
        Start/Stop
      </button>
    </div>
  );
};

render(<Timer />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>


推荐阅读