我正在我的本机应用程序中实现倒计时,但某些功能无法正常运行。

倒计时似乎每分钟损失1秒(如gif所示,它在33到31之间跳跃)

javascript - 倒数钩每分钟损失1秒-LMLPHP

这是代码:

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变量的原因),但实际上我不喜欢该解决方案。

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

最佳答案

目前,您假设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在幕后做了类似的事情,只是由于固定的超时导致漂移。

09-25 17:47