我正在我的本机应用程序中实现倒计时,但某些功能无法正常运行。
倒计时似乎每分钟损失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
变量的原因),但实际上我不喜欢该解决方案。有小费吗?错误在哪里?提前致谢。
最佳答案
目前,您假设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
在幕后做了类似的事情,只是由于固定的超时导致漂移。