在 React 中使用 Hooks 实现倒数计时器

IT技术 javascript reactjs react-hooks
2021-04-03 19:39:57

我试图用 React 钩子在屏幕上呈现倒数计时器,但我不确定呈现它的最佳方式。

我知道我应该使用 useEffect 将当前状态与以前的状态进行比较,但我认为我做得不对。

我将不胜感激!

我尝试了几种不同的方法,它们都不起作用,例如每当更新时设置状态,但它最终会像疯了一样闪烁。



const Timer = ({ seconds }) => {
    const [timeLeft, setTimeLeft] = useState('');

    const now = Date.now();
    const then = now + seconds * 1000;

    const countDown = setInterval(() => {
        const secondsLeft = Math.round((then - Date.now()) / 1000);
        if(secondsLeft <= 0) {
            clearInterval(countDown);
            console.log('done!');
            return;
        }
        displayTimeLeft(secondsLeft);
    }, 1000);

    const displayTimeLeft = seconds => {
        let minutesLeft = Math.floor(seconds/60) ;
        let secondsLeft = seconds % 60;
        minutesLeft = minutesLeft.toString().length === 1 ? "0" + minutesLeft : minutesLeft;
        secondsLeft = secondsLeft.toString().length === 1 ? "0" + secondsLeft : secondsLeft;
        return `${minutesLeft}:${secondsLeft}`;
    }

    useEffect(() => {
        setInterval(() => {
            setTimeLeft(displayTimeLeft(seconds));
        }, 1000);
    }, [seconds])


    return (
        <div><h1>{timeLeft}</h1></div>
    )
}

export default Timer;```
3个回答
const Timer = ({ seconds }) => {
  // initialize timeLeft with the seconds prop
  const [timeLeft, setTimeLeft] = useState(seconds);

  useEffect(() => {
    // exit early when we reach 0
    if (!timeLeft) return;

    // save intervalId to clear the interval when the
    // component re-renders
    const intervalId = setInterval(() => {
      setTimeLeft(timeLeft - 1);
    }, 1000);

    // clear interval on re-render to avoid memory leaks
    return () => clearInterval(intervalId);
    // add timeLeft as a dependency to re-rerun the effect
    // when we update it
  }, [timeLeft]);

  return (
    <div>
      <h1>{timeLeft}</h1>
    </div>
  );
};
@AmirShitrit 你是说setTimeout吗?两者都是有效的选项,但setTimeout假设触发一次函数,而setInterval假设每 x 时间触发一次函数。由于useEffect我们需要在每次timeLeft更改时设置和清除计时器的性质,我想它实际上并不像“真实”一样setIntervalsetTimeout在这种情况下我可以看到您的观点
2021-05-23 19:39:57
@AmirShitrit 是的,我更喜欢 setTimeout
2021-06-04 19:39:57
在这里使用 setTimer 更有意义吗?
2021-06-15 19:39:57
是的。我的意思是设置超时。谢谢!
2021-06-18 19:39:57
我最喜欢的是它的工作原理。
2021-06-20 19:39:57

你应该使用setInterval. 我只是想对@Asaf 解决方案稍加改进。您不必每次更改值时都重新设置间隔。它会删除间隔并每次添加一个新的间隔(setTimeout在这种情况下也可以使用 a )。所以你可以删除你的useEffect(即[]的依赖项

function Countdown({ seconds }) {
  const [timeLeft, setTimeLeft] = useState(seconds);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setTimeLeft((t) => t - 1);
    }, 1000);
    return () => clearInterval(intervalId);
  }, []);

  return <div>{timeLeft}s</div>;
}

工作示例:

倒计时示例

请注意,在 setter 中,我们需要使用此语法,(t) => t - 1以便我们每次都能获得最新值(请参阅:https : //reactjs.org/docs/hooks-reference.html#functional-updates)。


编辑 (22/10/2021)

如果您想使用 asetInterval并将计数器停止在 0,您可以执行以下操作:

function Countdown({ seconds }) {
  const [timeLeft, setTimeLeft] = useState(seconds);
  const intervalRef = useRef(); // Add a ref to store the interval id

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setTimeLeft((t) => t - 1);
    }, 1000);
    return () => clearInterval(intervalRef.current);
  }, []);

  // Add a listener to `timeLeft`
  useEffect(() => {
    if (timeLeft <= 0) {
      clearInterval(intervalRef.current);
    }
  }, [timeLeft]);

  return <div>{timeLeft}s</div>;
}

倒计时示例

间隔将无限运行
2021-05-22 19:39:57
事情就是这样,父级对 一无所知timeLeft,更新每次重新渲染的间隔非常好,并且在timeLeft达到某个点(例如 0)时为您提供执行操作的选项。您可以在状态更新程序回调中执行此操作,但就是这样在我看来只是丑陋
2021-05-22 19:39:57
如果您想在 0: 处停止,您可以通过卸载组件来停止计数器{seconds > 0 && <Countdown seconds={seconds} />}但确实,根据要求,它可能需要进行一些调整。如果您更新secondsin 道具,它也不会更新间隔值我只是想提出一个有效的替代方案setTimeout,使用setInterval(而不是在每次渲染时重置它)。
2021-05-29 19:39:57
ESLint 有一个规则react-hooks/exhaustive-deps,强制timeLeft在数组中添加依赖项但是,仍然同意不必每次都清除间隔。我想在那种情况下我将不得不使用setTimeout
2021-06-18 19:39:57

这是 setTimeout 的另一种选择

const useCountDown = (start) => {
  const [counter, setCounter] = useState(start);
  useEffect(() => {
    if (counter === 0) {
      return;
    }
    setTimeout(() => {
      setCounter(counter - 1);
    }, 1000);
  }, [counter]);
  return counter;
};

例子

编辑 fragrant-currying-512ky