This article is the seventh in a series of in-depth ahoos source code articles, which have been organized into document- address . I think it's not bad, give a star to support it, thanks.
Today we're going to talk about timers.
useInterval and useTimeout
Looking at the names, we can roughly know that their functions correspond to setInterval and setTimeout. What are the advantages compared to the latter?
First look useInterval
, the code is simple, as shown below:
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]);
}
The difference from setInterval is as follows:
- A third parameter can be supported, which enables immediate execution of our timer via immediate.
- When the delay is changed, the old timer will be automatically cleared and the new timer will be started at the same time.
- Through the return clearing mechanism of useEffect, developers do not need to pay attention to the logic of clearing the timer to avoid the problem of memory leakage . This is a point that many developers ignore.
useTimeout is very similar to the above, as shown below, without additional explanation:
function useTimeout(fn: () => void, delay: number | undefined): void {
const fnRef = useLatest(fn);
useEffect(() => {
// ...忽略部分代码
const timer = setTimeout(() => {
fnRef.current();
}, delay);
return () => {
clearTimeout(timer);
};
// 动态修改 delay 以实现定时器间隔变化与暂停。
}, [delay]);
}
Problems with setTimeout and setInterval
First of all, setTimeout and setInterval are the "two main forces" of macro tasks in the event loop, and their execution timing cannot be as accurate as we expected. It needs to wait for the execution of the previous task. For example, the second parameter of setTimeout below is set to 0, and it will not be executed immediately.
setTimeout(() => {
console.log('test');
}, 0)
In another case, setTimeout and setInterval behave differently when the browser is not visible (such as when minimized) and when different time intervals are set in different browsers. According to when the browser switches to other tabs or minimizes, is your js timer still on time? The practical conclusions of this article are as follows:
In Google Chrome, when the page is invisible, the minimum interval for setInterval is limited to 1s. The setInterval of the Firefox browser is consistent with the Google feature, but the IE browser has not optimized the performance of the setInterval when it is invisible, and the interval time before and after being invisible is unchanged.
In Google Chrome, setTimeout will become 1s if the interval is less than 1s when the browser is invisible, and it will become the interval value of N+1s if it is greater than or equal to 1s. The minimum interval time of setTimeout under Firefox browser will become 1s, and the interval greater than or equal to 1s will remain unchanged. The interval between IE browsers before and after the invisible state does not change.
This conclusion, I have not verified, but it seems that the difference is quite big, and another option is mentioned, which is requestAnimationFrame.
window.requestAnimationFrame() tells the browser that you want to perform an animation, and asks the browser to call the specified callback function to update the animation before the next repaint. This method needs to pass in a callback function as a parameter, the callback function will be executed before the browser's next repaint
In order to improve performance and battery life, in most browsers, when requestAnimationFrame() is running in a background tab or hidden <iframe>
, requestAnimationFrame() will be suspended to improve performance and battery life. .
Therefore, ahooks also provides hooks for analog timer processing using requestAnimationFrame
, let's take a look.
useRafInterval and useRafTimeout
Look directly useRafInterval
. (useRafTimeout is similar to useRafInterval, so I won't go into details here).
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]);
}
It can be seen that most of the code logic is the same as the previous useInterval, except that the setRafInterval
method is used regularly, and ---3574bb7e3bb643a2d32c59d34c274d6b clearRafInterval
is used to clear the timer.
setRafInterval
Go directly to the code:
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;
};
The first is to use typeof to judge the compatibility logic. If it is not compatible, use setInterval in the end.
Initially record a start time.
In the requestAnimationFrame callback, determine whether the current time minus the start time has reached the interval, and if so, execute our callback function. Update start time.
clearRafInterval
Clear the timer.
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);
};
If the cancelAnimationFrame
API is not supported, clear it through clearInterval, and if it supports it, use cancelAnimationFrame to clear it directly.
Thinking and Summarizing
Regarding timers, we usually use a lot, but some students often forget to clear the timer. Combined with the feature of useEffect
return to the function of clearing side effects, we can encapsulate this kind of logic into hooks, allowing developers to more convenient to use.
In addition, if you want to not execute the timer when the page is not visible, you can choose useRafInterval and useRafTimeout, which are implemented internally using requestAnimationFrame
.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。