React利用scheduler思想實(shí)現(xiàn)任務(wù)的打斷與恢復(fù)
前言
react 16.8
版本引入了fiber
架構(gòu),在fiber架構(gòu)的支持下,實(shí)現(xiàn)了并發(fā)渲染。
在16.8版本以前,在state
改變后,經(jīng)過(guò)一系列的流程,最終會(huì)進(jìn)入diff,在diff的時(shí)候,會(huì)采用DFS進(jìn)行遍歷虛擬dom樹,這個(gè)遍歷過(guò)程是一口氣
完成的,當(dāng)我們的應(yīng)用很復(fù)雜,元素比較多的時(shí)候,diff的過(guò)程往往會(huì)花費(fèi)很長(zhǎng)的時(shí)間,主線程此時(shí)正在進(jìn)行diif比較,如果diff事件超過(guò)16.6ms,會(huì)造成瀏覽器掉幀,如果時(shí)間很長(zhǎng),會(huì)造成頁(yè)面卡頓。
在react18版本,默認(rèn)開啟了并發(fā)模式,開發(fā)者可以使用useTransition
hook手動(dòng)開并發(fā)模式,在并發(fā)模式下,diff
過(guò)程是可中斷的,如果diff
時(shí)間超過(guò)5ms
,那么React會(huì)中斷此次diff
,會(huì)交出主線程的使用權(quán),從而讓瀏覽器去渲染,當(dāng)有空閑的時(shí)間的時(shí)候,會(huì)利用時(shí)間循環(huán)機(jī)制恢復(fù)diif
,從而提高大型應(yīng)用的用戶體驗(yàn)。并發(fā)模式下任務(wù)的打斷與恢復(fù)機(jī)制是scheduler
提供的,我們今天將探討下,如何利用React scheduler打斷一個(gè)耗時(shí)任務(wù),例如從0累加到2000萬(wàn)。
scheduler
從0累加到2000萬(wàn)
const work = () => { for(let currentIndex = 0; currentIndex < totalCount; currentIndex++) { sum += currentIndex } }
火焰圖
我們看上面的火焰圖,這個(gè)任務(wù)執(zhí)行了近100ms,左右,超過(guò)了瀏覽器的一幀16.6ms
,當(dāng)一個(gè)任務(wù)執(zhí)行時(shí)間超過(guò)50ms
時(shí),瀏覽器會(huì)任務(wù)這個(gè)任務(wù)時(shí)長(zhǎng)任務(wù),超過(guò)50ms的部分,火焰圖會(huì)標(biāo)記為紅色。
既然同步遍歷會(huì)造成瀏覽器掉幀,那么我們有什么辦法做到不讓瀏覽器掉幀呢?答案就是將這2000萬(wàn)個(gè)累加操作分成很小的task
,然后讓task
作為一個(gè)工作單元去執(zhí)行,當(dāng)執(zhí)行到一定的數(shù)量的時(shí)候,如果運(yùn)行時(shí)間超過(guò)了我們規(guī)定的時(shí)間,那么我們中斷這個(gè)任務(wù),在中斷的時(shí)候判斷下還有沒有任務(wù)要執(zhí)行,如果有任務(wù)要執(zhí)行,那么我們將這個(gè)任務(wù)執(zhí)行函數(shù)放入事件循環(huán)中,等下一次事件循環(huán)的時(shí)候,取出來(lái)執(zhí)行這個(gè)任務(wù)。
如何讓for循環(huán)中斷呢? 我們只需要在遍歷的時(shí)候判斷當(dāng)前的索引和數(shù)據(jù)總數(shù)以及抽象出一個(gè)方法,判斷應(yīng)不應(yīng)該終止(比如:我規(guī)定讓task累計(jì)運(yùn)行5ms,當(dāng)5ms用完的時(shí)候,就應(yīng)該終止)。
const getCurrentTime = () => Date.now(); // 調(diào)度應(yīng)該被中斷嗎 function shouldYieldToHost() { const timeElapsed = getCurrentTime() - startTime; // 如果當(dāng)前時(shí)間減去開始時(shí)間小于 5ms, 那么繼續(xù)調(diào)度 if (timeElapsed < 5) { return false; } return true; } const work = () => { for(let currentIndex = 0; currentIndex < totalCount && !shouldYieldToHost(); currentIndex++) { sum += currentIndex } }
如何判斷有沒有任務(wù)需要恢復(fù)呢? 我們將work
函數(shù)放在workloop
函數(shù)中去執(zhí)行,當(dāng)work
函數(shù)終止的時(shí)候,調(diào)用?;氐?code>workloop函數(shù)時(shí),判斷currentIndex < totalCount
,如果小于,那么我們 reutrn true
,告訴調(diào)用workloop
的函數(shù)還有任務(wù)需要執(zhí)行,需要將任務(wù)執(zhí)行函數(shù)放到異步任務(wù)中,等下一次事件循環(huán)的時(shí)候取出來(lái)執(zhí)行。
const flushWork = () => { return workLoop() } const workLoop = () => { while(true) { try { work() } catch (error) { }finally { if(currentIndex < totalCount) { return true } else { return false } } } }
任務(wù)恢復(fù)機(jī)制,利用事件循環(huán)原理
const performWorkUntilDeadline = () => { const currentTime = getCurrentTime(); startTime = currentTime; const hasTimeRemaining = true; // 有剩余時(shí)間 let hasMoreWork = true; try { // 這里執(zhí)行的函數(shù)就是 flushWork,flushWork 如果返回一個(gè) true 那么表示還有任務(wù) // 這里是 workLoop 循環(huán)里 return 的, 如果 return true, 那么表示還有剩余的任務(wù),只是時(shí)間用完了,被中斷了 hasMoreWork = flushWork(); } finally { //如果還有剩余任務(wù),調(diào)用schedulePerformWorkUntilDeadline將performWorkUntilDeadline 放入到異步任務(wù)里,等下一次事件循環(huán)被調(diào)用。 if (hasMoreWork) { schedulePerformWorkUntilDeadline(); } else { } } let schedulePerformWorkUntilDeadline; // react 中調(diào)度的優(yōu)先級(jí) setImmediate > MessageChannel > setTimeout if (typeof localSetImmediate === 'function') { schedulePerformWorkUntilDeadline = () => { localSetImmediate(performWorkUntilDeadline); }; } else if (typeof MessageChannel !== 'undefined') { const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; schedulePerformWorkUntilDeadline = () => { port.postMessage(null); }; } else { schedulePerformWorkUntilDeadline = () => { localSetTimeout(performWorkUntilDeadline, 0); }; }
完整實(shí)現(xiàn)代碼
const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null; const localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : null; const localSetImmediate = typeof setImmediate !== 'undefined' ? setImmediate : null; let startTime; // 記錄開始時(shí)間 let sum = 0 const currentIndex = 0; // 當(dāng)前遍歷的索引 const totalCount = 20000000 const getCurrentTime = () => Date.now(); // 調(diào)度應(yīng)該被中斷嗎 function shouldYieldToHost() { const timeElapsed = getCurrentTime() - startTime; // 如果當(dāng)前時(shí)間減去開始時(shí)間小于 5ms, 那么繼續(xù)調(diào)度 if (timeElapsed < 5) { return false; } return true; } const performWorkUntilDeadline = () => { const currentTime = getCurrentTime(); startTime = currentTime; const hasTimeRemaining = true; // 有剩余時(shí)間 let hasMoreWork = true; try { // 這里執(zhí)行的函數(shù)就是 flushWork,flushWork 如果返回一個(gè) true 那么表示還有任務(wù) // 這里的 是 workLoop 循環(huán)里 return 的, 如果 return true, 那么表示還有剩余的任務(wù),只是時(shí)間用完了,被中斷了 hasMoreWork = flushWork(); } finally { if (hasMoreWork) { schedulePerformWorkUntilDeadline(); } else { } } }; let schedulePerformWorkUntilDeadline; // react 中調(diào)度的優(yōu)先級(jí) setImmediate > MessageChannel > setTimeout if (typeof localSetImmediate === 'function') { schedulePerformWorkUntilDeadline = () => { localSetImmediate(performWorkUntilDeadline); }; } else if (typeof MessageChannel !== 'undefined') { const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; schedulePerformWorkUntilDeadline = () => { port.postMessage(null); }; } else { schedulePerformWorkUntilDeadline = () => { localSetTimeout(performWorkUntilDeadline, 0); }; } const flushWork = () => { return workLoop() } const workLoop = () => { while(true) { try { work() } catch (error) { }finally { if(currentIndex < totalCount) { return true } else { return false } } } } const work = () => { for(let currentIndex = 0; currentIndex < totalCount && !shouldYieldToHost(); currentIndex++) { sum += currentIndex } } performWorkUntilDeadline()
火焰圖
我們可以看到,上面執(zhí)行近100ms的任務(wù)被分成了很多很多小任務(wù),其中每個(gè)小任務(wù)執(zhí)行的時(shí)間是5ms左右。這樣我們就完成了利用react scheduler實(shí)現(xiàn)任務(wù)的打斷與恢復(fù)機(jī)制。
以上就是React利用scheduler思想實(shí)現(xiàn)任務(wù)的打斷與恢復(fù)的詳細(xì)內(nèi)容,更多關(guān)于React scheduler任務(wù)的打斷與恢復(fù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解React Angular Vue三大前端技術(shù)
當(dāng)前世界中,技術(shù)發(fā)展非常迅速并且變化迅速,開發(fā)者需要更多的開發(fā)工具來(lái)解決不同的問(wèn)題。本文就對(duì)于當(dāng)下主流的前端開發(fā)技術(shù)React、Vue、Angular這三個(gè)框架做個(gè)相對(duì)詳盡的探究,目的是為了解開這些前端技術(shù)的面紗,看看各自的廬山真面目。2021-05-05React中g(shù)etDefaultProps的使用小結(jié)
React中的getDefaultProps功能允許開發(fā)者為類組件定義默認(rèn)屬性,提高組件的靈活性和容錯(cuò)性,本文介紹了getDefaultProps的作用、語(yǔ)法以及最佳實(shí)踐,并探討了其他替代方案,如函數(shù)組件中的默認(rèn)參數(shù)、高階組件和ContextAPI等,理解這些概念有助于提升代碼的可維護(hù)性和用戶體驗(yàn)2024-09-09React和Vue實(shí)現(xiàn)文件下載進(jìn)度條
本文主要介紹了React和Vue實(shí)現(xiàn)文件下載進(jìn)度條,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04React Router 中實(shí)現(xiàn)嵌套路由和動(dòng)態(tài)路由的示例
React Router 是一個(gè)非常強(qiáng)大和靈活的路由庫(kù),它為 React 應(yīng)用程序提供了豐富的導(dǎo)航和 URL 管理功能,能夠幫助我們構(gòu)建復(fù)雜的單頁(yè)應(yīng)用和多頁(yè)應(yīng)用,這篇文章主要介紹了React Router 中如何實(shí)現(xiàn)嵌套路由和動(dòng)態(tài)路由,需要的朋友可以參考下2023-05-05React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03