首页 > 解决方案 > 倒计时钩每分钟损失 1 秒

问题描述

我正在我的 react-native 应用程序中实现倒计时,但有些东西无法正常工作。

倒计时似乎每分钟损失 1 秒(正如您在 gif 中看到的那样,它在 33 和 31 之间跳跃)

错误示例

这是代码:

import {
  differenceInDays,
  differenceInHours,
  differenceInMinutes,
  differenceInSeconds,
  isBefore,
  parseISO,
} from 'date-fns'
import { useEffect, useState } from 'react'

type CountdownResult = {
  days: number
  hours: number
  minutes: number
  seconds: number
}

const calculateInitialDuration = (endDate: string, today: Date): CountdownResult => {
  const futureDate = new Date(endDate)
  const days = differenceInDays(futureDate, today)
  const hours = differenceInHours(futureDate, today) % 24
  const minutes = differenceInMinutes(futureDate, today) % 60
  const seconds = differenceInSeconds(futureDate, today) % 60
  return { days, hours, minutes, seconds }
}

const EXPIREDRESULT: CountdownResult = { days: 0, hours: 0, minutes: 0, seconds: 0 }

// TODO: FIXME: sometimes the countdown jumps directly between 2 seconds
// even if the real time passed is 1 second
// this was happening before the refactor too
const useCountdown = (endDate: string): CountdownResult => {
  const today = new Date()
  const formattedEndDate = parseISO(endDate)
  // doing this because at the beginning countdown seems stuck on the first second
  // maybe there is a better solution for this problem
  const initialCountdown = calculateInitialDuration(endDate, today)
  initialCountdown.seconds++

  const [time, setTime] = useState(isBefore(formattedEndDate, today) ? EXPIREDRESULT : initialCountdown)

  useEffect(() => {
    if (isBefore(formattedEndDate, today)) return
    const intervalId = setInterval(() => {
      setTime(calculateInitialDuration(endDate, today))
    }, 1000)
    return (): void => clearInterval(intervalId)
  }, [time])
  return time
}

export default useCountdown

endDate是遵循ISO 8601格式的字符串。我正在使用date-fns但我也尝试了基本的 javascript 实现,错误仍然相同。

另一个奇怪的事情是倒计时,一开始,第一秒就卡住了一秒(这就是我创建initialCountdown变量的原因),但实际上我不喜欢这个解决方案。

有小费吗?错误在哪里?提前致谢。

标签: javascriptreactjsreact-native

解决方案


目前,您假设setInterval()每 1,000 毫秒触发一次回调。

    setInterval(() => {
      setTime(calculateInitialDuration(endDate, today))
    }, 1000)

不幸的是,浏览器必须做的所有其他事情,都不能保证它会。

为了获得更高的准确性,您需要做的是重复使用setTimeout()计算设置超时的时间。

let timeout;

const start = (() => {
  // IIFE because func needs to be able to reference itself!
  let func = () => {
    // Do whatever you need to do here
    let now = new Date();
    
    let timeToNextSecond = 1000 - (now.getTime() % 1000);
    console.log('Now: ', now, 'TimeToNext: ', timeToNextSecond);
    timeout = setTimeout(func, timeToNextSecond);
  };
  return func;
})();

const stop = () => clearTimeout(timeout);

start();
// wait 10 seconds(ish)
setTimeout(stop, 10000);

如果您运行此程序,您将看到后续超时在下一秒开始后不久运行。假设浏览器没有陷入其他事情,它将每秒运行一次。

想法:我想setInterval在幕后做这样的事情,只是有一个固定的超时导致漂移。


推荐阅读