当前位置:网站首页>如何让定时器在页面最小化的时候不执行?
如何让定时器在页面最小化的时候不执行?
2022-08-01 20:58:00 【GopalFeng】
本文是深入浅出 ahooks 源码系列文章的第七篇,这个系列的目标主要有以下几点:
- 加深对 React hooks 的理解。
- 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
- 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
注:本系列对 ahooks 的源码解析是基于 v3.3.13。自己 folk 了一份源码,主要是对源码做了一些解读,可见 详情[1]。
今天我们来聊聊定时器。
useInterval 和 useTimeout
看名称,我们就能大概知道,它们的功能对应的是 setInterval 和 setTimeout,那对比后者有什么优势?
先看 useInterval,代码简单,如下所示:
function useInterval(
fn: () => void,
delay: number | undefined,
options?: {
immediate?: boolean;
},
) {
const immediate = options?.immediate;
const fnRef = useLatest(fn);
useEffect(() => {
// 忽略部分代码...
// 立即执行
if (immediate) {
fnRef.current();
}
const timer = setInterval(() => {
fnRef.current();
}, delay);
// 清除定时器
return () => {
clearInterval(timer);
};
// 动态修改 delay 以实现定时器间隔变化与暂停。
}, [delay]);
}
跟 setInterval 的区别如下:
- 可以支持第三个参数,通过 immediate 能够立即执行我们的定时器。
- 在变更 delay 的时候,会自动清除旧的定时器,并同时启动新的定时器。
- 通过 useEffect 的返回清除机制,开发者不需要关注清除定时器的逻辑,避免内存泄露问题。这点是很多开发者会忽略的点。
useTimeout 跟上面很类似,如下所示,不再做额外解释:
function useTimeout(fn: () => void, delay: number | undefined): void {
const fnRef = useLatest(fn);
useEffect(() => {
// ...忽略部分代码
const timer = setTimeout(() => {
fnRef.current();
}, delay);
return () => {
clearTimeout(timer);
};
// 动态修改 delay 以实现定时器间隔变化与暂停。
}, [delay]);
}
setTimeout 和 setInterval 的问题
首先,setTimeout 和 setInterval 作为事件循环中宏任务的“两大主力”,它的执行时机不能跟我们预期一样准确的,它需要等待前面任务的执行。比如下面的 setTimeout 的第二个参数设置为 0,并不会立即执行。
setTimeout(() => {
console.log('test');
}, 0)
另外还有一种情况,setTimeout 和 setInterval 在浏览器不可见的时候(比如最小化的时候),不同的浏览器中设置不同的时间间隔的时候,其表现不一样。根据 当浏览器切换到其他标签页或者最小化时,你的js定时器还准时吗?[2] 这篇文章的实践结论如下:
谷歌浏览器中,当页面处于不可见状态时,setInterval 的最小间隔时间会被限制为 1s。火狐浏览器的 setInterval 和谷歌特性一致,但是 ie 浏览器没有对不可见状态时的 setInterval 进行性能优化,不可见前后间隔时间不变。
在谷歌浏览器中,setTimeout在浏览器不可见状态下间隔低于1s的会变为1s,大于等于1s的会变成N+1s的间隔值。火狐浏览器下setTimeout的最小间隔时间会变为1s,大于等于1s的间隔不变。ie浏览器在不可见状态前后的间隔时间不变。
这个结论,我没有验证过,但看起来差异挺大,其中还提到了另外一个选择,就是 requestAnimationFrame。
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的 <iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。
所以,ahooks 也提供了使用 requestAnimationFrame 进行模拟定时器处理的 hook,我们一起来看下。
useRafInterval 和 useRafTimeout
直接看 useRafInterval。(useRafTimeout 和 useRafInterval 类似,这里不展开细说)。
function useRafInterval(
fn: () => void,
delay: number | undefined,
options?: {
immediate?: boolean;
},
) {
const immediate = options?.immediate;
const fnRef = useLatest(fn);
useEffect(() => {
// 省略部分代码...
const timer = setRafInterval(() => {
fnRef.current();
}, delay);
return () => {
clearRafInterval(timer);
};
}, [delay]);
}
可以看到,跟前面的 useInterval 大部分代码逻辑都是一样的,只是定时使用了 setRafInterval 方法,清除定时器用了 clearRafInterval。
setRafInterval
直接上代码:
const setRafInterval = function (callback: () => void, delay: number = 0): Handle {
if (typeof requestAnimationFrame === typeof undefined) {
// 如果不支持,还是使用 setInterval
return {
id: setInterval(callback, delay),
};
}
// 开始时间
let start = new Date().getTime();
const handle: Handle = {
id: 0,
};
const loop = () => {
const current = new Date().getTime();
// 当前时间 - 开始时间,大于设置的间隔,则执行,并重置开始时间
if (current - start >= delay) {
callback();
start = new Date().getTime();
}
handle.id = requestAnimationFrame(loop);
};
handle.id = requestAnimationFrame(loop);
return handle;
};
首先是用 typeof 判断进行兼容逻辑处理,假如不兼容,则兜底使用 setInterval。
初始记录一个 start 的时间。
在 requestAnimationFrame 回调中,判断现在的时间减去开始时间有没有达到间隔,假如达到则执行我们的 callback 函数。更新开始时间。
clearRafInterval
清除定时器。
function cancelAnimationFrameIsNotDefined(t: any): t is NodeJS.Timer {
return typeof cancelAnimationFrame === typeof undefined;
}
// 清除定时器
const clearRafInterval = function (handle: Handle) {
if (cancelAnimationFrameIsNotDefined(handle.id)) {
return clearInterval(handle.id);
}
cancelAnimationFrame(handle.id);
};
假如不支持 cancelAnimationFrame API,则通过 clearInterval 清除,支持则直接使用 cancelAnimationFrame 清除。
思考与总结
关于定时器,我们平时用得不少,但经常有同学容易忘记清除定时器,结合 useEffect 返回清除副作用函数这个特性,我们可以将这类逻辑一起封装到 hook 中,让开发者使用更加方便。
另外,假如希望在页面不可见的时候,不执行定时器,可以选择 useRafInterval 和 useRafTimeout,其内部是使用 requestAnimationFrame 进行实现。
系列文章:
- 大家都能看得懂的源码(一)ahooks 整体架构篇[3]
- 如何使用插件化机制优雅的封装你的请求hook [4]
- ahooks 是怎么解决 React 的闭包问题的?[5]
- ahooks 是怎么解决用户多次提交问题?[6]
- ahooks 中那些控制“时机”的hook都是怎么实现的?[7]
- 如何让 useEffect 支持 async...await?[8]
参考资料
[1]详情: https://github.com/GpingFeng/hooks
[2]当浏览器切换到其他标签页或者最小化时,你的js定时器还准时吗?: https://juejin.cn/post/6899796711401586695#comment
[3]大家都能看得懂的源码(一)ahooks 整体架构篇: https://juejin.cn/post/7105396478268407815
[4]如何使用插件化机制优雅的封装你的请求hook : https://juejin.cn/post/7105733829972721677
[5]ahooks 是怎么解决 React 的闭包问题的?: https://juejin.cn/post/7106061970184339464
[6]ahooks 是怎么解决用户多次提交问题?: https://juejin.cn/post/7106461530232717326
[7]ahooks 中那些控制“时机”的hook都是怎么实现的?: https://juejin.cn/post/7107189225509879838
[8]如何让 useEffect 支持 async...await?: https://juejin.cn/post/7108675095958126629
边栏推荐
- Multithreaded producers and consumers
- LinkedList source code sharing
- 任务调度线程池基本介绍
- 有点奇怪!访问目的网址,主机能容器却不行
- 扣减库存方案
- Based on FPGA in any number of bytes (single-byte or multibyte) serial port (UART) to send (including source engineering)
- Kubernetes 如何实现组件高可用
- 【Untitled】
- 网红驼背矫正产品真的管用吗?如何预防驼背?医生说要这样做
- 和我一起写一个音乐播放器,听一首最伟大的作品
猜你喜欢

Little data on how to learn?Jida latest small learning data review, 26 PDF page covers the 269 - page document small data learning theory, method and application are expounded

Where should I prepare for the PMP exam in September?

Pytorch框架学习记录8——最大池化的使用

外骨骼机器人(七):标准步态数据库

Internet使用的网络协议是什么

Based on FPGA in any number of bytes (single-byte or multibyte) serial port (UART) to send (including source engineering)

【Social Media Marketing】How to know if your WhatsApp is blocked?

4.1 配置Mysql与注册登录模块

Excel advanced drawing techniques, 100 (22) - how to respectively the irregular data

OSG笔记:设置DO_NOT_COMPUTE_NEAR_FAR,手动计算远近平面
随机推荐
Get started quickly with MongoDB
【Untitled】
OSG笔记:设置DO_NOT_COMPUTE_NEAR_FAR,手动计算远近平面
字符串
iptables的使用简单测试
乐观锁批量跟新 纯SQL
Nacos 配置中心
Qt设置应用程序开机自启 解决设置失败原因
360借条安全专家:陌生微信好友不要轻易加贷款推广多是诈骗
数据库内核面试中我不会的问题(1)
tiup mirror clone
技能大赛训练:A部分加固题目
Which websites are commonly used for patent searches?
Addition, Subtraction, Multiplication of Large Integers, Multiplication and Division of Large Integers and Ordinary Integers
Redis does check-in statistics
Goroutine Leaks - The Forgotten Sender
C语言之字符串函数二
仿牛客论坛项目
[Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)
【Untitled】