深入理解React調(diào)度(Scheduler)原理
異步調(diào)度
問題:由于對于大型的 React 應(yīng)用,會存在一次更新,遞歸遍歷大量的虛擬 DOM ,造成占用 js 線程,使得瀏覽器沒有時(shí)間去做一些動畫效果,伴隨項(xiàng)目越來越大,項(xiàng)目會越來越卡。
對比Vue:
Vue 有這 template 模版收集依賴的過程,輕松構(gòu)建響應(yīng)式,使得在一次更新中,Vue 能夠迅速響應(yīng),找到需要更新的范圍,然后以組件粒度更新組件,渲染視圖。
React 中,一次更新 React 無法知道此次更新的波及范圍,所以 React 選擇從根節(jié)點(diǎn)開始 diff ,查找不同,更新這些不同。
解決:
把 React 的更新,交給瀏覽器自己控制,瀏覽器先執(zhí)行繪制任務(wù),空閑時(shí)間執(zhí)行更新任務(wù),解決了卡頓問題。即采用異步調(diào)度的方法。
時(shí)間分片
React讓瀏覽器控制React更新:瀏覽器每執(zhí)行一次事件循環(huán)都會:處理事件,執(zhí)行 js,調(diào)用requestAnimation
,布局 Layout,繪制 Paint,在一次執(zhí)行后,瀏覽器進(jìn)入空閑時(shí),可以執(zhí)行更新任務(wù)
谷歌瀏覽器提供的一個(gè) API, 在瀏覽器有空余的時(shí)間,瀏覽器就會調(diào)用 requestIdleCallback 的回調(diào)。
requestIdleCallback(callback,{ timeout })
- callback 回調(diào)。瀏覽器空余時(shí)間執(zhí)行回調(diào)函數(shù)。
- timeout 超時(shí)時(shí)間。如果瀏覽器長時(shí)間沒有空閑,那么回調(diào)就不會執(zhí)行,為了解決這個(gè)問題,可以通過 requestIdleCallback 的第二個(gè)參數(shù)指定一個(gè)超時(shí)時(shí)間。
React 為了防止 requestIdleCallback
中的任務(wù)由于瀏覽器沒有空閑時(shí)間而卡死,所以設(shè)置了 5 個(gè)優(yōu)先級。
Immediate
-1 需要立刻執(zhí)行。UserBlocking
250ms 超時(shí)時(shí)間250ms,一般指的是用戶交互。Normal
5000ms 超時(shí)時(shí)間5s,不需要直觀立即變化的任務(wù),比如網(wǎng)絡(luò)請求。Low
10000ms 超時(shí)時(shí)間10s,肯定要執(zhí)行的任務(wù),但是可以放在最后處理。Idle
一些沒有必要的任務(wù),可能不會執(zhí)行。
模擬requestdleCallback
條件:
- 可以主動讓出主線程,讓瀏覽器去渲染視圖。
- 一次事件循環(huán)只執(zhí)行一次,因?yàn)閳?zhí)行一個(gè)以后,還會請求下一次的時(shí)間片。
宏任務(wù):在下次事件循環(huán)中執(zhí)行,不會阻塞瀏覽器更新。且瀏覽器一次只會執(zhí)行一個(gè)宏任務(wù)。
1、采用setTimeout(fn, 0),間隔時(shí)間會變成 4 毫秒左右,不是最優(yōu)選方案
2、采用MessageChannel 接口,允許開發(fā)者創(chuàng)建一個(gè)新的消息通道,并通過它的兩個(gè) MessagePort 屬性發(fā)送數(shù)據(jù)。
在一次更新中,向?yàn)g覽器請求執(zhí)行更新任務(wù),調(diào)用 requesetHostCallbcak
,將更新任務(wù) 函數(shù)callback賦值給 scheduleHostCallback
,port2 向 port1 發(fā)起 postMessage消息通知。
port1 會通過 onmessage,接受來自 port2 消息,執(zhí)行更新任務(wù) scheduleHostCallback
,執(zhí)行完后,清空任務(wù)。
異步調(diào)度原理
React 發(fā)生一次更新,會統(tǒng)一走 ensureRootIsScheduled(調(diào)度應(yīng)用)
對于 正常更新 會走 performSyncWorkOnRoot 邏輯,最后會走 workLoopSync
。
對于 低優(yōu)先級的異步更新 會走 performConcurrentWorkOnRoot 邏輯,最后會走 workLoopConcurrent
。
區(qū)別:異步模式會調(diào)用一個(gè) shouldYield()
,如果當(dāng)前瀏覽器沒有空余時(shí)間, shouldYield 會中止循環(huán),直到瀏覽器有空閑時(shí)間后再繼續(xù)遍歷,從而達(dá)到終止渲染的目的。解決了一次性遍歷大量的 fiber ,導(dǎo)致瀏覽器沒有時(shí)間執(zhí)行一些渲染任務(wù),導(dǎo)致了頁面卡頓。
1、scheduleCallback
更新任務(wù)、異步更新任務(wù)都是由調(diào)度器 scheduleCallback
統(tǒng)一調(diào)度的
正常更新任務(wù):
scheduleCallback(Immediate,workLoopSync)
異步更新任務(wù):
/* 計(jì)算超時(shí)等級,就是如上那五個(gè)等級 */ var priorityLevel = inferPriorityFromExpirationTime(currentTime, expirationTime); scheduleCallback(priorityLevel,workLoopConcurrent)
scheduleCallback()
函數(shù)執(zhí)行過程
scheduleCallback 流程如下:
- 創(chuàng)建一個(gè)新的任務(wù) newTask。
- 通過任務(wù)的開始時(shí)間( startTime ) 和 當(dāng)前時(shí)間( currentTime ) 比較:當(dāng) startTime > currentTime, 說明未過期,存到 timerQueue,當(dāng) startTime <= currentTime,說明已過期, 存到 taskQueue。
- 如果任務(wù)沒有過期,用
requestHostTimeout
延時(shí)執(zhí)行handleTimeout
。 - 如果任務(wù)過期,并且沒有調(diào)度中的任務(wù),那么調(diào)度
requestHostCallback
。 - 本質(zhì)上調(diào)度的是
flushWork
。
2、requestHostTimeout
通過 setTimeout 來進(jìn)行延時(shí)指定時(shí)間的。
延時(shí)執(zhí)行 handleTimeout
,cancelHostTimeout
用于清除當(dāng)前的延時(shí)器。
3、handleTimeout
延時(shí)時(shí)間后,handleTimeout 會把任務(wù)重新放在 requestHostCallback 調(diào)度。
通過 advanceTimers
將 timerQueue 中過期的任務(wù)轉(zhuǎn)移到 taskQueue 中。然后調(diào)用 requestHostCallback
調(diào)度過期的任務(wù)。
4、advanceTimers
如果任務(wù)已經(jīng)過期,那么將 timerQueue 中的過期任務(wù),放入 taskQueue。
5、flushWork
requestHostCallback ,放入 MessageChannel 中的回調(diào)函數(shù)是flushWork。
flushWork 如果有延時(shí)任務(wù)執(zhí)行的話,那么會先暫停延時(shí)任務(wù),然后調(diào)用 workLoop
,去真正執(zhí)行超時(shí)的更新任務(wù)。
6、workLoop
workLoop 是調(diào)度中的 workLoop
React 的更新任務(wù)最后都是放在 taskQueue 中
workLoop 會依次更新過期任務(wù)隊(duì)列中的任務(wù)
調(diào)度流程圖
總結(jié)
1、異步調(diào)度原因
2、時(shí)間分片和 requestIdleCallback
3、異步調(diào)度原理
4、調(diào)度流程
到此這篇關(guān)于深入理解React 調(diào)度(Scheduler)原理的文章就介紹到這了,更多相關(guān)React 調(diào)度內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?TypeScript?應(yīng)用中便捷使用Redux?Toolkit方法詳解
這篇文章主要為大家介紹了React?TypeScript?應(yīng)用中便捷使用Redux?Toolkit方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11React項(xiàng)目中className運(yùn)用及問題解決
這篇文章主要為大家介紹了React項(xiàng)目中className運(yùn)用及問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12React自定義視頻全屏按鈕實(shí)現(xiàn)全屏功能
這篇文章主要介紹了React自定義視頻全屏按鈕實(shí)現(xiàn)全屏功能,通過繪制全屏按鈕,并綁定點(diǎn)擊事件,編寫點(diǎn)擊事件,通過實(shí)例代碼給大家詳細(xì)講解,需要的朋友可以參考下2022-11-11react-beautiful-dnd 實(shí)現(xiàn)組件拖拽功能
這篇文章主要介紹了react-beautiful-dnd 實(shí)現(xiàn)組件拖拽功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08React內(nèi)部實(shí)現(xiàn)cache方法示例詳解
這篇文章主要為大家介紹了React內(nèi)部實(shí)現(xiàn)cache方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Webpack 4.x搭建react開發(fā)環(huán)境的方法步驟
這篇文章主要介紹了Webpack 4.x搭建react開發(fā)環(huán)境的方法步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08