您好,登錄后才能下訂單哦!
本篇內容主要講解“如何理解React中的高優先級任務插隊機制”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何理解React中的高優先級任務插隊機制”吧!
點擊進入React源碼調試倉庫。
在React的concurrent模式下,低優先級任務執行過程中,一旦有更高優先級的任務進來,那么這個低優先級的任務會被取消,優先執行高優先級任務。等高優先級任務做完了,低優先級任務會被重新做一遍。
我們用一個具體的例子來理解一下高優先級任務插隊。
有這樣一個組件,state為0,進入頁面,會調用setState將state加1,這個作為低優先級任務。React開始進行更新,在這個低優先級任務尚未完成時,模擬按鈕點擊,state加2,這個作為高優先級任務。可以看到,頁面上的數字變化為0 -> 2 -> 3,而不是0 -> 1 -> 3。這就說明,當低優先級任務(加1)正在進行時,高優先級任務進來了,而它會把state設置為2。由于高優先級任務的插隊,設置state為1的低優先級任務會被取消,先做高優先級任務,所以數字從0變成了2。而高優先級任務完成之后,低優先級任務會被重做,所以state再從2加到了3。
現象如下:
利用chrome的性能分析工具捕捉更新過程,可以明顯看到優先級插隊的過程
完整的profile文件我保存下來了,可以載入到chrome中詳細查看:高優先級插隊.json 。
可以再看一下這個過程中兩個任務優先級在調度過程中的信息
點擊查看 高優先級插隊示例代碼文件。
點擊查看 低優先級任務饑餓問題示例代碼文件。
接下來我們就來從setState開始,探討一下這種插隊行為的本質,內容涉及update對象的生成、發起調度、工作循環、高優任務插隊、update對象的處理、低優先級任務重做等內容。
產生更新
當調用setState時,意味著組件對應的fiber節點產生了一個更新。setState實際上是生成一個update對象,調用enqueueSetState,將這個update對象連接到fiber節點的updateQueue鏈表中.
Component.prototype.setState = function(partialState, callback) { this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
enqueueSetState的職責是創建update對象,將它入隊fiber節點的update鏈表(updateQueue),然后發起調度。
enqueueSetState(inst, payload, callback) { // 獲取當前觸發更新的fiber節點。inst是組件實例 const fiber = getInstance(inst); // eventTime是當前觸發更新的時間戳 const eventTime = requestEventTime(); const suspenseConfig = requestCurrentSuspenseConfig(); // 獲取本次update的優先級 const lane = requestUpdateLane(fiber, suspenseConfig); // 創建update對象 const update = createUpdate(eventTime, lane, suspenseConfig); // payload就是setState的參數,回調函數或者是對象的形式。 // 處理更新時參與計算新狀態的過程 update.payload = payload; // 將update放入fiber的updateQueue enqueueUpdate(fiber, update); // 開始進行調度 scheduleUpdateOnFiber(fiber, lane, eventTime); }
梳理一下enqueueSetState中具體做的事情:
找到fiber
首先獲取產生更新的組件所對應的fiber節點,因為產生的update對象需要放到fiber節點的updateQueue上。然后獲取當前這個update產生的時間,這與更新的饑餓問題相關,我們暫且不考慮,而且下一步的suspenseConfig可以先忽略。
計算優先級
之后比較重要的是計算當前這個更新它的優先級lane:
const lane = requestUpdateLane(fiber, suspenseConfig);
計算這個優先級的時候,是如何決定根據什么東西去計算呢?這還得從React的合成事件說起。
事件觸發時,合成事件機制調用scheduler中的runWithPriority函數,目的是以該交互事件對應的事件優先級去派發真正的事件流程。runWithPriority會將事件優先級轉化為scheduler內部的優先級并記錄下來。當調用requestUpdateLane計算lane的時候,會去獲取scheduler中的優先級,以此作為lane計算的依據。
這部分的源碼在這里
創建update對象, 入隊updateQueue
根據lane和eventTime還有suspenseConfig,去創建一個update對象,結構如下:
const update: Update<*> = { eventTime, lane, suspenseConfig, tag: UpdateState, payload: null, callback: null, next: null, };
eventTime:更新的產生時間
lane:表示優先級
suspenseConfig:任務掛起相關
tag:表示更新是哪種類型(UpdateState,ReplaceState,ForceUpdate,CaptureUpdate)
payload:更新所攜帶的狀態。
在類組件中,有兩種可能,對象({}),和函數((prevState, nextProps):newState => {})
根組件中,為React.element,即ReactDOM.render的第一個參數
callback:可理解為setState的回調
next:指向下一個update的指針
再之后就是去調用React任務執行的入口函數:scheduleUpdateOnFiber去調度執行更新任務了。
現在我們知道了,產生更新的fiber節點上會有一個updateQueue,它包含了剛剛產生的update。下面該進入scheduleUpdateOnFiber了,開始進入真正的調度流程。通過調用scheduleUpdateOnFiber,render階段的構建workInProgress樹的任務會被調度執行,這個過程中,fiber上的updateQueue會被處理。
調度準備
React的更新入口是scheduleUpdateOnFiber,它區分update的lane,將同步更新和異步更新分流,讓二者進入各自的流程。但在此之前,它會做幾個比較重要的工作:
檢查是否是無限更新,例如在render函數中調用了setState。
從產生更新的節點開始,往上一直循環到root,目的是將fiber.lanes一直向上收集,收集到父級節點的childLanes中,childLanes是識別這個fiber子樹是否需要更新的關鍵。
在root上標記更新,也就是將update的lane放到root.pendingLanes中,每次渲染的優先級基準:renderLanes就是取自root.pendingLanes中最緊急的那一部分lanes。
這三步可以視為更新執行前的準備工作。
第1個可以防止死循環卡死的情況。
第2個,如果fiber.lanes不為空,則說明該fiber節點有更新,而fiber.childLanes是判斷當前子樹是否有更新的重要依據,若有更新,則繼續向下構建,否則直接復用已有的fiber樹,就不往下循環了,可以屏蔽掉那些無需更新的fiber節點。
第3個是將當前update對象的lane加入到root.pendingLanes中,保證真正開始做更新任務的時候,獲取到update的lane,從而作為本次更新的渲染優先級(renderLanes),去更新。
實際上在更新時候獲取到的renderLanes,并不一定包含update對象的lane,因為有可能它只是一個較低優先級的更新,有可能在它前面有高優先級的更新
梳理完scheduleUpdateOnFiber的大致邏輯之后,我們來看一下它的源碼:
export function scheduleUpdateOnFiber( fiber: Fiber, lane: Lane, eventTime: number, ) { // 第一步,檢查是否有無限更新 checkForNestedUpdates(); ... // 第二步,向上收集fiber.childLanes const root = markUpdateLaneFromFiberToRoot(fiber, lane); ... // 第三步,在root上標記更新,將update的lane放到root.pendingLanes markRootUpdated(root, lane, eventTime); ... // 根據Scheduler的優先級獲取到對應的React優先級 const priorityLevel = getCurrentPriorityLevel(); if (lane === SyncLane) { // 本次更新是同步的,例如傳統的同步渲染模式 if ( (executionContext & LegacyUnbatchedContext) !== NoContext && (executionContext & (RenderContext | CommitContext)) === NoContext ) { // 如果是本次更新是同步的,并且當前還未渲染,意味著主線程空閑,并沒有React的 // 更新任務在執行,那么調用performSyncWorkOnRoot開始執行同步任務 ... performSyncWorkOnRoot(root); } else { // 如果是本次更新是同步的,不過當前有React更新任務正在進行, // 而且因為無法打斷,所以調用ensureRootIsScheduled // 目的是去復用已經在更新的任務,讓這個已有的任務 // 把這次更新順便做了 ensureRootIsScheduled(root, eventTime); ... } } else { ... // Schedule other updates after in case the callback is sync. // 如果是更新是異步的,調用ensureRootIsScheduled去進入異步調度 ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, lane); } ... }
scheduleUpdateOnFiber 的完整源碼在這里,這里是第二步:markUpdateLaneFromFiberToRoot 和 第三步: markRootUpdated的完整源碼,我都做了注釋。
經過了前面的準備工作后,scheduleUpdateOnFiber最終會調用ensureRootIsScheduled,來讓React任務被調度,這是一個非常重要的函數,它關乎同等或較低任務的收斂、
高優先級任務插隊和任務饑餓問題,下面詳細講解它。
開始調度
在開始講解ensureRootIsScheduled之前,我們有必要弄清楚React的更新任務的本質。
React任務的本質
一個update的產生最終會使React在內存中根據現有的fiber樹構建一棵新的fiber樹,新的state的計算、diff操作、以及一些生命周期的調用,都會在這個構建過程中進行。這個整體的構建工作被稱為render階段,這個render階段整體就是一個完整的React更新任務,更新任務可以看作執行一個函數,這個函數在concurrent模式下就是performConcurrentWorkOnRoot,更新任務的調度可以看成是這個函數被scheduler按照任務優先級安排它何時執行。
Scheduler的調度和React的調度是兩個完全不同的概念,React的調度是協調任務進入哪種Scheduler的調度模式,它的調度并不涉及任務的執行,而Scheduler是調度機制的真正核心,它是實打實地去執行任務,沒有它,React的任務再重要也無法執行,希望讀者加以區分這兩種概念。
當一個任務被調度之后,scheduler就會生成一個任務對象(task),它的結構如下所示,除了callback之外暫時不需要關心其他字段的含義。
var newTask = { id: taskIdCounter++, // 任務函數,也就是 performConcurrentWorkOnRoot callback, // 任務調度優先級,由即將講到的任務優先級轉化而來 priorityLevel, // 任務開始執行的時間點 startTime, // 任務的過期時間 expirationTime, // 在小頂堆任務隊列中排序的依據 sortIndex: -1, };
每當生成了一個這樣的任務,它就會被掛載到root節點的callbackNode屬性上,以表示當前已經有任務被調度了,同時會將任務優先級存儲到root的callbackPriority上,
表示如果有新的任務進來,必須用它的任務優先級和已有任務的優先級(root.callbackPriority)比較,來決定是否有必要取消已經有的任務。
所以在調度任務的時候,任務優先級是不可或缺的一個重要角色。
任務優先級
任務本身是由更新產生的,因此任務優先級本質上是和update的優先級,即update.lane有關(只是有關,不一定是由它而來)。得出的任務優先級屬于lanePriority,它不是update的lane,而且與scheduler內部的調度優先級是兩個概念,React中的優先級轉化關系可以看我總結過的一篇文章:React中的優先級,我們這里只探討任務優先級的生成過程。
在 調度準備 的最后提到過,update.lane會被放入root.pendingLanes,隨后會獲取root.pendingLanes中最優先級的那些lanes作為renderLanes。任務優先級的生成就發生在計算renderLanes的階段,任務優先級其實就是renderLanes對應的lanePriority。因為renderLanes是本次更新的優先級基準,所以它對應的lanePriority被作為任務優先級來衡量本次更新任務的優先級權重理所應當。
root.pendingLanes,包含了當前fiber樹中所有待處理的update的lane。
任務優先級有三類:
同步優先級:React傳統的同步渲染模式產生的更新任務所持有的優先級
同步批量優先級:同步模式到concurrent模式過渡模式:blocking模式(介紹)產生的更新任務所持有的優先級
concurrent模式下的優先級:concurrent模式產生的更新持有的優先級
最右面的兩個lane分別為同步優先級和同步批量優先級,剩下左邊的lane幾乎所有都和concurrent模式有關。
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010; concurrent模式下的lanes:/* */ 0b1111111111111111111111111111100;
計算renderLanes的函數是getNextLanes,生成任務優先級的函數是getHighestPriorityLanes
任務優先級決定著任務在React中被如何調度,而由任務優先級轉化成的任務調度優先級(上面給出的scheduler的task結構中的priorityLevel),
決定著Scheduler何時去處理這個任務。
任務調度協調 - ensureRootIsScheduled
目前為止我們了解了任務和任務優先級的本質,下面正式進入任務的調度過程。React這邊對任務的調度本質上其實是以任務優先級為基準,去操作多個或單個任務。
多個任務的情況,相對于新任務,會對現有任務進行或復用,或取消的操作,單個任務的情況,對任務進行或同步,或異步,或批量同步(暫時不需要關注) 的調度決策,
這種行為可以看成是一種任務調度協調機制,這種協調通過ensureRootIsScheduled去實現。
讓我們看一看ensureRootIsScheduled函數做的事情,先是準備本次任務調度協調所需要的lanes和任務優先級,然后判斷是否真的需要調度
獲取root.callbackNode,即舊任務
檢查任務是否過期,將過期任務放入root.expiredLanes,目的是讓過期任務能夠以同步優先級去進入調度(立即執行)
獲取renderLanes(優先從root.expiredLanes獲取),如果renderLanes是空的,說明不需要調度,直接return掉
獲取本次任務,即新任務的優先級:newCallbackPriority
接下來是協調任務調度的過程:
首先判斷是否有必要發起一次新調度,方法是通過比較新任務的優先級和舊任務的優先級是否相等:
相等,則說明無需再次發起一次調度,直接復用舊任務即可,讓舊任務在處理更新的時候順便把新任務給做了。
不相等,則說明新任務的優先級一定高于舊任務,這種情況就是高優先級任務插隊,需要把舊任務取消掉。
真正發起調度,看新任務的任務優先級:
同步優先級:調用scheduleSyncCallback去同步執行任務。
同步批量執行:調用scheduleCallback將任務以立即執行的優先級去加入調度。
屬于concurrent模式的優先級:調用scheduleCallback將任務以上面獲取到的新任務優先級去加入調度。
這里有兩點需要說明:
1. 為什么新舊任務的優先級如果不相等,那么新任務的優先級一定高于舊任務?
這是因為每次調度去獲取任務優先級的時候,都只獲取root.pendingLanes中最緊急的那部分lanes對應的優先級,低優先級的update持有的lane對應的優先級是無法被獲取到的。通過這種辦法,可以將來自同一事件中的多個更新收斂到一個任務中去執行,言外之意就是同一個事件觸發的多次更新的優先級是一樣的,沒必要發起多次任務調度。例如在一個事件中多次調用setState:
class Demo extends React.Component { state = { count: 0 } onClick = () => { this.setState({ count: 1 }) this.setState({ count: 2 }) } render() { return <button onClick={onClick}>{this.state.count}</button> } }
頁面上會直接顯示出2,雖然onClick事件調用了兩次setState,但只會引起一次調度,設置count為2的那次調度被因為優先級與設置count為1的那次任務的優先級相同,
所以沒有去再次發起調度,而是復用了已有任務。這是React17對于多次setState優化實現的改變,之前是通過batchingUpdate這種機制實現的。
1. 三種任務優先級的調度模式有何區別,行為表現上如何?
同步優先級:傳統的React同步渲染模式和過期任務的調度。通過React提供的scheduleSyncCallback函數將任務函數performSyncWorkOnRoot加入到React自己的同步隊列(syncQueue)中,之后以ImmediateSchedulerPriority的優先級將循環執行syncQueue的函數加入到scheduler中,目的是讓任務在下一次事件循環中被執行掉。但是因為React的控制,這種模式下的時間片會在任務都執行完之后再去檢查,表現為沒有時間片。
同步批量執行:同步渲染模式到concurrent渲染模式的過渡模式blocking模式,會將任務函數performSyncWorkOnRoot以ImmediateSchedulerPriority的優先級加入到scheduler中,也是讓任務在下一次事件循環中被執行掉,也不會有時間片的表現。
屬于concurrent模式的優先級:將任務函數performConcurrentWorkOnRoot以任務自己的優先級加入到scheduler中,scheduler內部的會通過這個優先級控制該任務在scheduler內部任務隊列中的排序,從而決定任務合適被執行,而且任務真正執行時會有時間片的表現,可以發揮出scheduler異步可中斷調度的真正威力。
要注意一點,用來做新舊任務比較的優先級與這里將任務加入到scheduler中傳入的優先級不是一個,后者可由前者通過lanePriorityToSchedulerPriority轉化而來。
經過以上的分析,相信大家已經對ensureRootIsScheduled的運行機制比較清晰了,現在讓我們看一下它的實現:
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { // 獲取舊任務 const existingCallbackNode = root.callbackNode; // 記錄任務的過期時間,檢查是否有過期任務,有則立即將它放到root.expiredLanes, // 便于接下來將這個任務以同步模式立即調度 markStarvedLanesAsExpired(root, currentTime); // 獲取renderLanes const nextLanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes, ); // 獲取renderLanes對應的任務優先級 const newCallbackPriority = returnNextLanesPriority(); if (nextLanes === NoLanes) { // 如果渲染優先級為空,則不需要調度 if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); root.callbackNode = null; root.callbackPriority = NoLanePriority; } return; } // 如果存在舊任務,那么看一下能否復用 if (existingCallbackNode !== null) { // 獲取舊任務的優先級 const existingCallbackPriority = root.callbackPriority; // 如果新舊任務的優先級相同,則無需調度 if (existingCallbackPriority === newCallbackPriority) { return; } // 代碼執行到這里說明新任務的優先級高于舊任務的優先級 // 取消掉舊任務,實現高優先級任務插隊 cancelCallback(existingCallbackNode); } // 調度一個新任務 let newCallbackNode; if (newCallbackPriority === SyncLanePriority) { // 若新任務的優先級為同步優先級,則同步調度,傳統的同步渲染和過期任務會走這里 newCallbackNode = scheduleSyncCallback( performSyncWorkOnRoot.bind(null, root), ); } else if (newCallbackPriority === SyncBatchedLanePriority) { // 同步模式到concurrent模式的過渡模式:blocking模式會走這里 newCallbackNode = scheduleCallback( ImmediateSchedulerPriority, performSyncWorkOnRoot.bind(null, root), ); } else { // concurrent模式的渲染會走這里 // 根據任務優先級獲取Scheduler的調度優先級 const schedulerPriorityLevel = lanePriorityToSchedulerPriority( newCallbackPriority, ); // 計算出調度優先級之后,開始讓Scheduler調度React的更新任務 newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root), ); } // 更新root上的任務優先級和任務,以便下次發起調度時候可以獲取到 root.callbackPriority = newCallbackPriority; root.callbackNode = newCallbackNode; }
ensureRootIsScheduled實際上是在任務調度層面整合了高優先級任務的插隊和任務饑餓問題的關鍵邏輯,這只是宏觀層面的決策,決策背后的原因是React處理更新時
對于不同優先級的update的取舍以及對root.pendingLanes的標記操作,這需要我們下沉到執行更新任務的過程中。
處理更新
一旦有更新產生,update對象就會被放入updateQueue并掛載到fiber節點上。構建fiber樹時,會帶著renderLanes去處理updateQueue,在beginWork階段,對于類組件
會調用processUpdateQueue函數,逐個處理這個鏈表上的每個update對象,計算新的狀態,一旦update持有的優先級不夠,那么就會跳過這個update的處理,并把這個被跳過的update的lane放到fiber.lanes中,好在completeWork階段收集起來。
循環updateQueue去計算狀態的過程實際上較為復雜,因為低優先級update會被跳過并且會重做,所以這涉及到最終狀態統一的問題,關于這一過程的原理解讀在我的這篇文章里:扒一扒React計算狀態的原理,在本篇文章中只關注優先級相關的部分。
關于優先級的部分比較好理解,就是只處理優先級足夠的update,跳過那些優先級不足的update,并且將這些update的lane放到fiber.lanes中。我們直接來看一下實現:
function processUpdateQueue<State>( workInProgress: Fiber, props: any, instance: any, renderLanes: Lanes, ): void { ... if (firstBaseUpdate !== null) { let update = firstBaseUpdate; do { const updateupdateLane = update.lane; // isSubsetOfLanes函數的意義是,判斷當前更新的優先級(updateLane) // 是否在渲染優先級(renderLanes)中如果不在,那么就說明優先級不足 if (!isSubsetOfLanes(renderLanes, updateLane)) { ... /* * * newLanes會在最后被賦值到workInProgress.lanes上,而它又最終 * 會被收集到root.pendingLanes。 * * 再次更新時會從root上的pendingLanes中找出應該在本次中更新的優先 * 級(renderLanes),renderLanes含有本次跳過的優先級,再次進入, * processUpdateQueue wip的優先級符合要求,被更新掉,低優先級任務 * 因此被重做 * */ newLanes = mergeLanes(newLanes, updateLane); } else { // 優先級足夠,去計算state ... } } while (true); // 將newLanes賦值給workInProgress.lanes, // 就是將被跳過的update的lane放到fiber.lanes workInProgress.lanes = newLanes; } }
只處理優先級足夠的update是讓高優先級任務被執行掉的最本質原因,在循環了一次updateQueue之后,那些被跳過的update的lane又被放入了fiber.lanes,現在,只需要將它放到root.pendingLanes中,就能表示在本輪更新后,仍然有任務未被處理,從而實現低優先級任務被重新調度。所以接下來的過程就是fiber節點的完成階段:completeWork階段去收集這些lanes。
收集未被處理的lane
在completeUnitOfWork的時候,fiber.lanes 和 childLanes被一層一層收集到父級fiber的childLanes中,該過程發生在completeUnitOfWork函數中調用的resetChildLanes,它循環fiber節點的子樹,將子節點及其兄弟節點中的lanes和childLanes收集到當前正在complete階段的fiber節點上的childLanes。
假設第3層中的<List/>和<Table/>組件都分別有update因為優先級不夠而被跳過,那么在它們父級的div fiber節點completeUnitOfWork的時候,會調用resetChildLanes
把它倆的lanes收集到div fiber.childLanes中,最終把所有的lanes收集到root.pendingLanes.
root(pendingLanes: 0b01110) | 1 App | | 2 compeleteUnitOfWork-----------> div (childLanes: 0b01110) / / 3 <List/> ---------> <Table/> --------> p (lanes: 0b00010) (lanes: 0b00100) (childLanes: 0b01000) / / / / / 4 p ul / / li ------> li
在每一次往上循環的時候,都會調用resetChildLanes,目的是將fiber.childLanes層層收集。
function completeUnitOfWork(unitOfWork: Fiber): void { // 已經結束beginWork階段的fiber節點被稱為completedWork let completedWork = unitOfWork; do { // 向上一直循環到root的過程 ... // fiber節點的.flags上沒有Incomplete,說明是正常完成了工作 if ((completedWork.flags & Incomplete) === NoFlags) { ... // 調用resetChildLanes去收集lanes resetChildLanes(completedWork); ... } else {/*...*/} ... } while (completedWork !== null); ... }
resetChildLanes中只收集當前正在complete的fiber節點的子節點和兄弟節點的lanes以及childLanes:
function resetChildLanes(completedWork: Fiber) { ... let newChildLanes = NoLanes; if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) { // profile相關,無需關注 } else { // 循環子節點和兄弟節點,收集lanes let child = completedWork.child; while (child !== null) { // 收集過程 newChildLanes = mergeLanes( newChildLanes, mergeLanes(child.lanes, child.childLanes), ); childchild = child.sibling; } } // 將收集到的lanes放到該fiber節點的childLanes中 completedWork.childLanes = newChildLanes; }
最后將這些收集到的childLanes放到root.pendingLanes的過程,是發生在本次更新的commit階段中,因為render階段的渲染優先級來自root.pendingLanes,不能隨意地修改它。所以要在render階段之后的commit階段去修改。我們看一下commitRootImpl中這個過程的實現:
function commitRootImpl(root, renderPriorityLevel) { // 將收集到的childLanes,連同root自己的lanes,一并賦值給remainingLanes let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); // markRootFinished中會將remainingLanes賦值給remainingLanes markRootFinished(root, remainingLanes); ... }
重新發起調度
至此,我們將低優先級任務的lane重新收集到了root.pendingLanes中,這時只需要再發起一次調度就可以了,通過在commit階段再次調用ensureRootIsScheduled去實現,這樣就又會走一遍調度的流程,低優先級任務被執行。
function commitRootImpl(root, renderPriorityLevel) { // 將收集到的childLanes,連同root自己的lanes,一并賦值給remainingLanes let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); // markRootFinished中會將remainingLanes賦值給remainingLanes markRootFinished(root, remainingLanes); ... // 在每次所有更新完成的時候都會調用這個ensureRootIsScheduled // 以保證root上任何的pendingLanes都能被處理 ensureRootIsScheduled(root, now()); }
總結
高優先級任務插隊,低優先級任務重做的整個過程共有四個關鍵點:
ensureRootIsScheduled取消已有的低優先級更新任務,重新調度一個任務去做高優先級更新,并以root.pendingLanes中最重要的那部分lanes作為渲染優先級
執行更新任務時跳過updateQueue中的低優先級update,并將它的lane標記到fiber.lanes中。
fiber節點的complete階段收集fiber.lanes到父級fiber的childLanes,一直到root。
commit階段將所有root.childLanes連同root.lanes一并賦值給root.pendingLanes。
commit階段的最后重新發起調度。
整個流程始終以高優先級任務為重,顧全大局,最能夠體現React提升用戶體驗的決心。
到此,相信大家對“如何理解React中的高優先級任務插隊機制”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。