引言:计时器的困惑
PM:嘿,你来看一下这个倒计时组件,为什么时间总是跳得这么不准确?我们的用户可是每一秒都在盯着看呢!
FE:嗯,这个问题我也注意到了。我猜想可能是因为JavaScript的定时器不够精确所导致的。你知道的,JavaScript的定时器并不是真正的实时。
PM:不是实时?那我们的促销活动岂不是要泡汤了?用户体验可不能因为这个而受到影响啊!
FE:放心吧,我知道这很重要。我会深入调查具体原因,并找到一个解决方案的。我们的倒计时组件会准时如一,准确无误!
PM:那就拜托了!我可不想看到用户因为这个小bug而离我们而去。
所以,让我们揭开这个“时间误差”的谜团,并确保我们的倒计时组件能够精准无误地帮助用户倒数每一秒!
正文:倒计时组件的误差之谜
当你在开发这个功能时,你可能会首先写出像下面这样的代码:
import { FC, useState, useEffect } from "react";
import "./_style.scss";
/** @description 每秒剩余秒数减1 */
export const CountOne: FC<{
startTime: number;
}> = (props) => {
const { startTime } = props;
const [restSec, setRestSec] = useState(startTime);
useEffect(() => {
// 创建一个定时器,每隔1秒执行一次
const inter = setInterval(() => {
// 更新restSec状态,每秒减一
setRestSec(oldVal => oldVal - 1);
}, 1000);
return () => {
clearInterval(inter);
};
}, []);
return (
<div className="pub-style">
<p className="pub-style-text">每秒减一方案:</p>
<p className="pub-style-text">
<span className="pub-style-text-span">倒计时秒数:</span>
<span className="pub-style-text-span">{restSec}</span>
</p>
</div>
);
};
每秒剩余秒数减1,这个逻辑看似并无问题。然而,经过一段时间的运行,或者在电脑合盖后再运行,你可能会发现实际值与期望值之间存在较大的偏差。
![](https://img.haomeiwen.com/i11893612/ed47003a789d51d8.png)
如图所示,第一种方案即为执行一段时间后上述代码的执行结果。是不是很离谱?
这到底是哪里出了问题呢?于是,我把这段代码拿去询问ChatGPT,给出的回答如下:
-
JavaScript定时器的不精确性
- JavaScript中的
setTimeout
和setInterval
函数并不能保证完全精确的时间间隔,因为它们受到了事件循环的影响。如果页面或者浏览器忙于其他任务,定时器的回调可能会被推迟执行。
- JavaScript中的
-
浏览器的最小化或标签页切换
- 当用户最小化浏览器或切换到其他标签页时,浏览器可能会减慢或暂停定时器的回调,以节省资源。
-
页面渲染的性能问题
- 如果页面存在复杂的DOM操作或者JavaScript执行效率低下,可能会影响倒计时组件的更新频率。
怎么办:倒计时组件的误差之谜与解决策略
当我们尝试用JavaScript来实现倒计时功能时,通常会使用setInterval
函数。然而,setInterval
函数并不能保证完全精确的时间间隔,因为它的执行受到很多因素的影响,比如页面的渲染速度、浏览器的性能等。
第一种方案:使用Web Workers
Web Workers允许我们在后台线程中运行代码,避免了主线程的拥堵,可以提高定时器的准确性。但是,Web Workers无法访问DOM,因此不适用于需要实时渲染的场景。
第二种方案:时间戳比对
我们可以通过存储一个初始时间戳,并在每次回调中比对当前时间戳,以计算真实经过的时间。这种方法比单纯依赖setInterval
的回调更为精确。修改后的代码如下:
import { FC, useState, useEffect } from "react";
import { getUnixTime } from "date-fns";
import "./_style.scss";
/** @description 每秒都用deadline - now */
export const CountUseNow: FC<{
deadlineTime: number;
}> = (props) => {
const { deadlineTime } = props;
const [restSec, setRestSec] = useState(
deadlineTime - getUnixTime(Date.now())
);
useEffect(() => {
// 每隔1秒(1000毫秒)执行一次
// setRestSec(deadlineTime - getUnixTime(Date.now()))
// 操作,使得restSec的值每秒更新一次
const inter = setInterval(() => {
setRestSec(() => deadlineTime - getUnixTime(Date.now()));
}, 1000);
return () => {
clearInterval(inter);
};
}, []);
return (
<div className="pub-style">
<p className="pub-style-text">每秒都用deadline减now方案:</p>
<p className="pub-style-text">
<span className="pub-style-text-span">倒计时秒数:</span>
<span className="pub-style-text-span">{restSec}</span>
</p>
</div>
);
};
以上代码中,我们使用了getUnixTime
函数来获取当前时间的秒级时间戳,然后将其与设定的截止时间进行比对,从而得到剩余时间。
第三种方案:使用requestAnimationFrame
对于需要高频更新的倒计时,我们可以使用requestAnimationFrame
。它能保证在每次浏览器重绘之前更新时间,从而提高准确性。但是,在非动画场景下,使用频率较低。
第四种方案:优化页面性能
确保DOM操作和JavaScript执行尽可能高效,以减少对倒计时组件更新的干扰。这需要针对具体的应用场景进行优化,无法作为通用的解决方案。
经过比较,第二种方案修改成本较低且满足预期,更适合在大多数情况下使用。当然,具体选择哪种方案还需要根据实际的应用场景来决定。
结论:追求精准的倒计时
我们探讨了前端开发中倒计时组件出现误差的原因,并提供了几种解决方法。通过这些技术手段,我们可以极大地提高倒计时的准确性,确保用户体验不受影响。现在,我们可以告别那些“崩溃中”的时刻,迎接一个更加精准和可靠的倒计时体验。
致谢
ChatGPT 与 文心一言对本文亦有贡献
网友评论