为什么 clearInterval 没有停止我的计时器?

Why is clearInterval not stopping my timer?

我正在尝试构建一个具有三个按钮的计时器,一个是开始按钮,一个是停止按钮,它在当前整数处停止,还有一个重置按钮。我在下面添加了导致以下问题的代码。

我以为我的停止功能会阻止计时器递减,但它会继续这样做。此外,当在控制台中记录我的计时器状态时,您可以看到它没有在控制台中更新,即使它在 DOM 中更新。这是为什么?

感谢您的任何见解。

import React from 'react';
import './style.css';

export default function App() {
  const [timer, setTimer] = React.useState(50);

  const reset = () => {
    setTimer(50);
  };

  const start = () => {
    setTimer((prev) => prev - 1);
  };
  // const interval = setInterval(() => {
  //   console.log(updated)
  //   //start() }, 1000)
  //  }

  const interval = () => {
    setInterval(() => {
      console.log('updated');
      console.log(timer);
      start();
    }, 1000);
  };

  const stop = () => {
    clearInterval(start);
  };

  return (
    <div>
      <h1>{timer}</h1>
      <button onClick={interval}>start</button>
      <button onClick={stop}>stop</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}`

我认为您不应该将 setTimeout 包装到可调用对象中。然后你就失去了启动和停止它的能力,因为变量不引用间隔而是引用包装间隔的可调用对象。

看看这个指南:https://www.w3schools.com/jsref/met_win_clearinterval.asp

您在为间隔分配实际值时遇到了一个小问题。

这是它应该如何使用

const interval = setInterval(() => {})

clearInterval(interval)

对于您的代码更改,您可以创建一个 ref 来保留区间变量,并在以后使用它来清理区间。

function App() {
  const [timer, setTimer] = React.useState(5);
  const intervalRef = React.useRef(); //create a ref for interval

  const reset = () => {
    setTimer(5);
  };

  const start = () => {
    setTimer((prev) => {
       if(prev === 0) {
          stop();
          return 0;
       }
       return prev - 1;
    });
  };
  // const interval = setInterval(() => {
  //   console.log(updated)
  //   //start() }, 1000)
  //  }

  const interval = () => {
    //assign interval ref here
    intervalRef.current = setInterval(() => {
      start();
    }, 1000);
  };

  const stop = () => {
    //clear the interval ref
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <h1>{timer}</h1>
      <button onClick={interval}>start</button>
      <button onClick={stop}>stop</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

clearTimeoutclearInterval 各取一个令牌,该令牌由先前调用 setTimeoutsetInterval 返回。所以您需要存储该令牌:

const id = setInterval(() => console.log("triggered"), 1000);
// ...
clearInterval(id)

此外,你应该注意如果 App 是 re-rendered 会发生什么,所以你应该将 set/clear 逻辑放在 useEffect 这样你就可以清理你的间隔了。

此外,虽然您没有询问,但您的 console.log(timer) 不会起作用,并且将始终打印 50。该回调中的 timer 变量被捕获一次,并且是从未更新,因为该回调现在就在 setInterval 内。每次 App re-renders 时,您都需要使用更新的回调函数清除并重置间隔,或者使用您不断更新的 ref,这很痛苦。

我建议借用这个为您考虑所有这些事情的自定义挂钩:https://usehooks-ts.com/react-hook/use-interval

那么你的 App 组件会变得非常简单,但仍然很健壮:

const { useEffect, useRef, useState, useLayoutEffect } = React;

// https://usehooks-ts.com/react-hook/use-interval
function useInterval(callback: () => void, delay: number | null) {
  const savedCallback = useRef(callback);

  useLayoutEffect(() => {
    savedCallback.current = callback;
  }, [callback])

  useEffect(() => {
    if (!delay && delay !== 0) return;
    const id = setInterval(() => savedCallback.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}

function App() {
  const [timer, setTimer] = useState(50);
  const [running, setRunning] = useState(false);
  
  useInterval(() => setTimer(t => t - 1), running ? 1000 : null);
  
  const start = () => setRunning(true);
  const stop = () => setRunning(false);
  const reset = () => { setTimer(50); };

  return (
    <div>
      <h1>{timer}</h1><button onClick={start}>start</button>
      <button onClick={stop}> stop </button>
      <button onClick={reset}> reset </button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("react"));
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>