【问题标题】:Implementing a countdown timer in React with Hooks在 React 中使用 Hooks 实现倒数计时器
【发布时间】:2019-11-29 21:39:47
【问题描述】:

我正在尝试使用反应挂钩在屏幕上呈现倒计时计时器,但我不确定呈现它的最佳方式。

我知道我应该使用 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;```

【问题讨论】:

标签: javascript reactjs react-hooks


【解决方案1】:
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>
  );
};

【讨论】:

  • 在这里使用 setTimer 是否更有意义?
  • @AmirShitrit 你的意思是setTimeout?两者都是有效的选项,但setTimeout 假设触发一次函数,而setInterval 假设每 x 时间触发一次函数。由于useEffect 的性质,我们需要在每次timeLeft 更改时设置和清除计时器,我猜它的行为并不像“真实”setInterval,我可以在setTimeout 中看到你的观点这个案例。
  • 是的。我的意思是设置超时。谢谢!
  • @AmirShitrit 是的,我更喜欢 setTimeout
  • 我最喜欢它的地方是它可以工作。
【解决方案2】:

您应该使用setInterval。我只是想对@Asaf 解决方案进行一点改进。 您不必每次更改值时都重新设置间隔。它会删除间隔并每次添加一个新间隔(在这种情况下最好使用setTimeout)。所以你可以删除你的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) =&gt; t - 1这样的语法,这样我们每次都能得到最新的值(见:https://reactjs.org/docs/hooks-reference.html#functional-updates)。


编辑(2021 年 10 月 22 日)

如果您想使用 setInterval 并将计数器停止在 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>;
}

【讨论】:

  • 区间将无限运行
  • 如果您想在 0 处停止,您可以通过卸载组件来停止计数器:{seconds &gt; 0 &amp;&amp; &lt;Countdown seconds={seconds} /&gt;}。但确实,根据要求,它可能需要一些调整。如果您更新道具中的seconds,它也不会更新间隔值。我只是想提供一个有效的替代setTimeout,使用setInterval(而不是在每次渲染时重置它)。
  • 就是这样,父母对timeLeft一无所知,更新每次重新渲染的间隔非常好,并且当timeLeft达到某个点时,您可以选择做事像 0。你可以在状态更新回调中做到这一点,但在我看来这很难看
  • ESLint 有一个规则 react-hooks/exhaustive-deps 强制在数组中添加 timeLeft 的依赖项。但是,仍然同意不必每次都清除间隔。我认为在这种情况下我将不得不使用setTimeout
【解决方案3】:

这是 setTimeout 的另一种选择

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

例子

【讨论】:

    猜你喜欢
    • 2020-09-29
    • 2020-04-11
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多