本文是深入淺出 ahooks 原始碼系列文章的第七篇,該系列已整理成檔案-地址,覺得還不錯,給個 star 支持一下哈,Thanks,
今天我們來聊聊定時器,
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定時器還準時嗎? 這篇文章的實踐結論如下:
谷歌瀏覽器中,當頁面處于不可見狀態時,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 進行實作,
本文已收錄到個人博客中,歡迎關注~
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/502034.html
標籤:其他
上一篇:Vue組件的繼承用法
