定時(shí)器在頁面最小化時(shí)不執(zhí)行實(shí)現(xiàn)示例
引言
本文是深入淺出 ahooks 源碼系列文章的第七篇,這個(gè)系列的目標(biāo)主要有以下幾點(diǎn):
- 加深對(duì) React hooks 的理解。
- 學(xué)習(xí)如何抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫。
- 培養(yǎng)閱讀學(xué)習(xí)源碼的習(xí)慣,工具庫是一個(gè)對(duì)源碼閱讀不錯(cuò)的選擇。
注:本系列對(duì) ahooks 的源碼解析是基于 v3.3.13
。自己 folk 了一份源碼,主要是對(duì)源碼做了一些解讀,可見 詳情。
今天我們來聊聊定時(shí)器。
useInterval 和 useTimeout
看名稱,我們就能大概知道,它們的功能對(duì)應(yīng)的是 setInterval 和 setTimeout,那對(duì)比后者有什么優(yōu)勢?
先看 useInterval
,代碼簡單,如下所示:
function useInterval( fn: () => void, delay: number | undefined, options?: { immediate?: boolean; }, ) { const immediate = options?.immediate; const fnRef = useLatest(fn); useEffect(() => { // 忽略部分代碼... // 立即執(zhí)行 if (immediate) { fnRef.current(); } const timer = setInterval(() => { fnRef.current(); }, delay); // 清除定時(shí)器 return () => { clearInterval(timer); }; // 動(dòng)態(tài)修改 delay 以實(shí)現(xiàn)定時(shí)器間隔變化與暫停。 }, [delay]); }
跟 setInterval 的區(qū)別如下:
- 可以支持第三個(gè)參數(shù),通過 immediate 能夠立即執(zhí)行我們的定時(shí)器。
- 在變更 delay 的時(shí)候,會(huì)自動(dòng)清除舊的定時(shí)器,并同時(shí)啟動(dòng)新的定時(shí)器。
- 通過 useEffect 的返回清除機(jī)制,開發(fā)者不需要關(guān)注清除定時(shí)器的邏輯,避免內(nèi)存泄露問題。這點(diǎn)是很多開發(fā)者會(huì)忽略的點(diǎn)。
useTimeout 跟上面很類似,如下所示,不再做額外解釋:
function useTimeout(fn: () => void, delay: number | undefined): void { const fnRef = useLatest(fn); useEffect(() => { // ...忽略部分代碼 const timer = setTimeout(() => { fnRef.current(); }, delay); return () => { clearTimeout(timer); }; // 動(dòng)態(tài)修改 delay 以實(shí)現(xiàn)定時(shí)器間隔變化與暫停。 }, [delay]); }
setTimeout 和 setInterval 的問題
首先,setTimeout 和 setInterval 作為事件循環(huán)中宏任務(wù)的“兩大主力”,它的執(zhí)行時(shí)機(jī)不能跟我們預(yù)期一樣準(zhǔn)確的,它需要等待前面任務(wù)的執(zhí)行。比如下面的 setTimeout 的第二個(gè)參數(shù)設(shè)置為 0,并不會(huì)立即執(zhí)行。
setTimeout(() => { console.log('test'); }, 0)
另外還有一種情況,setTimeout 和 setInterval 在瀏覽器不可見的時(shí)候(比如最小化的時(shí)候),不同的瀏覽器中設(shè)置不同的時(shí)間間隔的時(shí)候,其表現(xiàn)不一樣。根據(jù) 當(dāng)瀏覽器切換到其他標(biāo)簽頁或者最小化時(shí),你的js定時(shí)器還準(zhǔn)時(shí)嗎? 這篇文章的實(shí)踐結(jié)論如下:
谷歌瀏覽器中,當(dāng)頁面處于不可見狀態(tài)時(shí),setInterval 的最小間隔時(shí)間會(huì)被限制為 1s?;鸷鼮g覽器的 setInterval 和谷歌特性一致,但是 ie 瀏覽器沒有對(duì)不可見狀態(tài)時(shí)的 setInterval 進(jìn)行性能優(yōu)化,不可見前后間隔時(shí)間不變。
在谷歌瀏覽器中,setTimeout在瀏覽器不可見狀態(tài)下間隔低于1s的會(huì)變?yōu)?s,大于等于1s的會(huì)變成N+1s的間隔值?;鸷鼮g覽器下setTimeout的最小間隔時(shí)間會(huì)變?yōu)?s,大于等于1s的間隔不變。ie瀏覽器在不可見狀態(tài)前后的間隔時(shí)間不變。
這個(gè)結(jié)論,我沒有驗(yàn)證過,但看起來差異挺大,其中還提到了另外一個(gè)選擇,就是 requestAnimationFrame。
window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個(gè)動(dòng)畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行
為了提高性能和電池壽命,因此在大多數(shù)瀏覽器里,當(dāng)requestAnimationFrame() 運(yùn)行在后臺(tái)標(biāo)簽頁或者隱藏的 <iframe>
里時(shí),requestAnimationFrame() 會(huì)被暫停調(diào)用以提升性能和電池壽命。
所以,ahooks 也提供了使用 requestAnimationFrame
進(jìn)行模擬定時(shí)器處理的 hook,我們一起來看下。
useRafInterval 和 useRafTimeout
直接看 useRafInterval
。(useRafTimeout 和 useRafInterval 類似,這里不展開細(xì)說)。
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 大部分代碼邏輯都是一樣的,只是定時(shí)使用了 setRafInterval
方法,清除定時(shí)器用了 clearRafInterval
。
setRafInterval
直接上代碼:
const setRafInterval = function (callback: () => void, delay: number = 0): Handle { if (typeof requestAnimationFrame === typeof undefined) { // 如果不支持,還是使用 setInterval return { id: setInterval(callback, delay), }; } // 開始時(shí)間 let start = new Date().getTime(); const handle: Handle = { id: 0, }; const loop = () => { const current = new Date().getTime(); // 當(dāng)前時(shí)間 - 開始時(shí)間,大于設(shè)置的間隔,則執(zhí)行,并重置開始時(shí)間 if (current - start >= delay) { callback(); start = new Date().getTime(); } handle.id = requestAnimationFrame(loop); }; handle.id = requestAnimationFrame(loop); return handle; };
首先是用 typeof 判斷進(jìn)行兼容邏輯處理,假如不兼容,則兜底使用 setInterval。
初始記錄一個(gè) start 的時(shí)間。
在 requestAnimationFrame 回調(diào)中,判斷現(xiàn)在的時(shí)間減去開始時(shí)間有沒有達(dá)到間隔,假如達(dá)到則執(zhí)行我們的 callback 函數(shù)。更新開始時(shí)間。
clearRafInterval
清除定時(shí)器。
function cancelAnimationFrameIsNotDefined(t: any): t is NodeJS.Timer { return typeof cancelAnimationFrame === typeof undefined; } // 清除定時(shí)器 const clearRafInterval = function (handle: Handle) { if (cancelAnimationFrameIsNotDefined(handle.id)) { return clearInterval(handle.id); } cancelAnimationFrame(handle.id); };
假如不支持 cancelAnimationFrame
API,則通過 clearInterval 清除,支持則直接使用 cancelAnimationFrame 清除。
思考與總結(jié)
關(guān)于定時(shí)器,我們平時(shí)用得不少,但經(jīng)常有同學(xué)容易忘記清除定時(shí)器,結(jié)合 useEffect
返回清除副作用函數(shù)這個(gè)特性,我們可以將這類邏輯一起封裝到 hook 中,讓開發(fā)者使用更加方便。
另外,假如希望在頁面不可見的時(shí)候,不執(zhí)行定時(shí)器,可以選擇 useRafInterval 和 useRafTimeout,其內(nèi)部是使用 requestAnimationFrame
進(jìn)行實(shí)現(xiàn)。
以上就是定時(shí)器在頁面最小化時(shí)不執(zhí)行實(shí)現(xiàn)示例的詳細(xì)內(nèi)容,更多關(guān)于定時(shí)器頁面最小化不執(zhí)行的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Skypack布局前端基建實(shí)現(xiàn)過程詳解
這篇文章主要為大家介紹了Skypack布局前端基建過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序 action-sheet 反饋上拉菜單簡單實(shí)例
這篇文章主要介紹了微信小程序 action-sheet 反饋上拉菜單簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05微信小程序 setData使用方法及常用錯(cuò)誤解決辦法
這篇文章主要介紹了微信小程序 setData使用方法及常用錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-05-05競態(tài)條件Race condition及如何避免的三種方案詳解
這篇文章主要為大家介紹了競態(tài)條件Race condition及如何避免的三種方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10微信小程序 圖片寬度自適應(yīng)的實(shí)現(xiàn)
這篇文章主要介紹了微信小程序 圖片寬度自適應(yīng)的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-04-04微信小程序 獲取當(dāng)前地理位置和經(jīng)緯度實(shí)例代碼
這篇文章主要介紹了微信小程序 獲取當(dāng)前地理位置和經(jīng)緯度實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-12-12