精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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鏈表中. 

  1. Component.prototype.setState = function(partialState, callback) {  
  2.   this.updater.enqueueSetState(this, partialState, callback, 'setState');  
  3. }; 

enqueueSetState的職責是創建update對象,將它入隊fiber節點的update鏈表(updateQueue),然后發起調度。 

  1. enqueueSetState(inst, payload, callback) {  
  2.     // 獲取當前觸發更新的fiber節點。inst是組件實例  
  3.     const fiber = getInstance(inst);  
  4.     // eventTime是當前觸發更新的時間戳  
  5.     const eventTime = requestEventTime();  
  6.     const suspenseConfig = requestCurrentSuspenseConfig();  
  7.     // 獲取本次update的優先級  
  8.     const lane = requestUpdateLane(fiber, suspenseConfig);  
  9.     // 創建update對象  
  10.     const update = createUpdate(eventTime, lane, suspenseConfig);  
  11.     // payload就是setState的參數,回調函數或者是對象的形式。  
  12.     // 處理更新時參與計算新狀態的過程  
  13.     update.payload = payload;  
  14.     // 將update放入fiber的updateQueue  
  15.     enqueueUpdate(fiber, update);  
  16.     // 開始進行調度 
  17.      scheduleUpdateOnFiber(fiber, lane, eventTime);  
  18.   } 

梳理一下enqueueSetState中具體做的事情:

找到fiber

首先獲取產生更新的組件所對應的fiber節點,因為產生的update對象需要放到fiber節點的updateQueue上。然后獲取當前這個update產生的時間,這與更新的饑餓問題相關,我們暫且不考慮,而且下一步的suspenseConfig可以先忽略。

計算優先級

之后比較重要的是計算當前這個更新它的優先級lane: 

  1. const lane = requestUpdateLane(fiber, suspenseConfig); 

計算這個優先級的時候,是如何決定根據什么東西去計算呢?這還得從React的合成事件說起。

事件觸發時,合成事件機制調用scheduler中的runWithPriority函數,目的是以該交互事件對應的事件優先級去派發真正的事件流程。runWithPriority會將事件優先級轉化為scheduler內部的優先級并記錄下來。當調用requestUpdateLane計算lane的時候,會去獲取scheduler中的優先級,以此作為lane計算的依據。

這部分的源碼在這里

創建update對象, 入隊updateQueue

根據lane和eventTime還有suspenseConfig,去創建一個update對象,結構如下: 

  1. const update: Update<*> = {  
  2.   eventTime,  
  3.   lane,  
  4.   suspenseConfig,  
  5.   tag: UpdateState,  
  6.   payload: null,  
  7.   callback: null,  
  8.   next: null,  
  9. }; 
  •  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的大致邏輯之后,我們來看一下它的源碼: 

  1. export function scheduleUpdateOnFiber(  
  2.   fiber: Fiber,  
  3.   lane: Lane,  
  4.   eventTime: number,  
  5. ) {  
  6.   // 第一步,檢查是否有無限更新  
  7.   checkForNestedUpdates();  
  8.   ...  
  9.   // 第二步,向上收集fiber.childLanes  
  10.   const root = markUpdateLaneFromFiberToRoot(fiber, lane); 
  11.   ...  
  12.   // 第三步,在root上標記更新,將update的lane放到root.pendingLanes  
  13.   markRootUpdated(root, lane, eventTime);  
  14.   ...  
  15.   // 根據Scheduler的優先級獲取到對應的React優先級  
  16.   const priorityLevel = getCurrentPriorityLevel();  
  17.   if (lane === SyncLane) {  
  18.     // 本次更新是同步的,例如傳統的同步渲染模式  
  19.     if (  
  20.       (executionContext & LegacyUnbatchedContext) !== NoContext &&  
  21.       (executionContext & (RenderContext | CommitContext)) === NoContext  
  22.     ) {  
  23.       // 如果是本次更新是同步的,并且當前還未渲染,意味著主線程空閑,并沒有React的  
  24.       // 更新任務在執行,那么調用performSyncWorkOnRoot開始執行同步任務 
  25.        ...  
  26.       performSyncWorkOnRoot(root);  
  27.     } else {  
  28.       // 如果是本次更新是同步的,不過當前有React更新任務正在進行,  
  29.       // 而且因為無法打斷,所以調用ensureRootIsScheduled 
  30.       // 目的是去復用已經在更新的任務,讓這個已有的任務  
  31.       // 把這次更新順便做了  
  32.       ensureRootIsScheduled(root, eventTime); 
  33.       ... 
  34.      }  
  35.   } else {  
  36.     ...  
  37.     // Schedule other updates after in case the callback is sync.  
  38.     // 如果是更新是異步的,調用ensureRootIsScheduled去進入異步調度  
  39.     ensureRootIsScheduled(root, eventTime);  
  40.     schedulePendingInteractions(root, lane);  
  41.   }  
  42.   ...  

    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之外暫時不需要關心其他字段的含義。 

  1. var newTask = {  
  2.    id: taskIdCounter++,  
  3.    // 任務函數,也就是 performConcurrentWorkOnRoot  
  4.    callback,  
  5.    // 任務調度優先級,由即將講到的任務優先級轉化而來  
  6.    priorityLevel,  
  7.    // 任務開始執行的時間點  
  8.    startTime,  
  9.    // 任務的過期時間  
  10.    expirationTime,  
  11.    // 在小頂堆任務隊列中排序的依據  
  12.    sortIndex: -1,  
  13.  }; 

每當生成了一個這樣的任務,它就會被掛載到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模式有關。 

  1. export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;  
  2. export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;  
  3. 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: 

  1. class Demo extends React.Component {  
  2.   state = {  
  3.     count: 0  
  4.   }  
  5.   onClick = () => {  
  6.     this.setState({ count: 1 })  
  7.     this.setState({ count: 2 })  
  8.   }  
  9.   render() {  
  10.     return <button onClick={onClick}>{this.state.count}</button>  
  11.   }  

頁面上會直接顯示出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的運行機制比較清晰了,現在讓我們看一下它的實現: 

  1. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {  
  2.   // 獲取舊任務  
  3.   const existingCallbackNode = root.callbackNode;  
  4.   // 記錄任務的過期時間,檢查是否有過期任務,有則立即將它放到root.expiredLanes,  
  5.   // 便于接下來將這個任務以同步模式立即調度  
  6.   markStarvedLanesAsExpired(root, currentTime);  
  7.   // 獲取renderLanes  
  8.   const nextLanes = getNextLanes 
  9.     root,  
  10.     root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,  
  11.   );  
  12.   // 獲取renderLanes對應的任務優先級  
  13.   const newCallbackPriority = returnNextLanesPriority();  
  14.   if (nextLanes === NoLanes) {  
  15.     // 如果渲染優先級為空,則不需要調度  
  16.     if (existingCallbackNode !== null) {  
  17.       cancelCallback(existingCallbackNode);  
  18.       root.callbackNode = null 
  19.       root.callbackPriority = NoLanePriority 
  20.     }  
  21.     return;  
  22.   }  
  23.   // 如果存在舊任務,那么看一下能否復用  
  24.   if (existingCallbackNode !== null) {  
  25.     // 獲取舊任務的優先級  
  26.     const existingCallbackPriority = root.callbackPriority;  
  27.     // 如果新舊任務的優先級相同,則無需調度  
  28.     if (existingCallbackPriority === newCallbackPriority) {  
  29.       return;  
  30.     }  
  31.     // 代碼執行到這里說明新任務的優先級高于舊任務的優先級  
  32.     // 取消掉舊任務,實現高優先級任務插隊  
  33.     cancelCallback(existingCallbackNode);  
  34.   }  
  35.   // 調度一個新任務  
  36.   let newCallbackNode;  
  37.   if (newCallbackPriority === SyncLanePriority) {  
  38.     // 若新任務的優先級為同步優先級,則同步調度,傳統的同步渲染和過期任務會走這里  
  39.     newCallbackNode = scheduleSyncCallback 
  40.       performSyncWorkOnRoot.bind(null, root),  
  41.     );  
  42.   } else if (newCallbackPriority === SyncBatchedLanePriority) {  
  43.     // 同步模式到concurrent模式的過渡模式:blocking模式會走這里  
  44.     newCallbackNode = scheduleCallback 
  45.       ImmediateSchedulerPriority,  
  46.       performSyncWorkOnRoot.bind(null, root),  
  47.     );  
  48.   } else {  
  49.     // concurrent模式的渲染會走這里  
  50.     // 根據任務優先級獲取Scheduler的調度優先級  
  51.     const schedulerPriorityLevel = lanePriorityToSchedulerPriority 
  52.       newCallbackPriority, 
  53.     );  
  54.     // 計算出調度優先級之后,開始讓Scheduler調度React的更新任務  
  55.     newCallbackNode = scheduleCallback 
  56.       schedulerPriorityLevel,  
  57.       performConcurrentWorkOnRoot.bind(null, root),  
  58.     );  
  59.   }  
  60.   // 更新root上的任務優先級和任務,以便下次發起調度時候可以獲取到  
  61.   root.callbackPriority = newCallbackPriority 
  62.   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中。我們直接來看一下實現: 

  1. function processUpdateQueue<State> 
  2.   workInProgress: Fiber,  
  3.   props: any,  
  4.   instance: any,  
  5.   renderLanes: Lanes,  
  6. ): void {  
  7.   ...  
  8.   if (firstBaseUpdate !== null) {  
  9.     let update = firstBaseUpdate 
  10.     do {  
  11.       const updateupdateLane = update.lane;  
  12.       // isSubsetOfLanes函數的意義是,判斷當前更新的優先級(updateLane)  
  13.       // 是否在渲染優先級(renderLanes)中如果不在,那么就說明優先級不足  
  14.       if (!isSubsetOfLanes(renderLanes, updateLane)) {  
  15.         ...  
  16.         /*  
  17.         *  
  18.         * newLanes會在最后被賦值到workInProgress.lanes上,而它又最終  
  19.         * 會被收集到root.pendingLanes。  
  20.         *  
  21.         * 再次更新時會從root上的pendingLanes中找出應該在本次中更新的優先  
  22.         * 級(renderLanes),renderLanes含有本次跳過的優先級,再次進入,  
  23.         * processUpdateQueue wip的優先級符合要求,被更新掉,低優先級任務  
  24.         * 因此被重做  
  25.         * */  
  26.         newLanes = mergeLanes(newLanes, updateLane);  
  27.       } else {  
  28.         // 優先級足夠,去計算state  
  29.         ... 
  30.       }  
  31.     } while (true);  
  32.     // 將newLanes賦值給workInProgress.lanes,  
  33.     // 就是將被跳過的update的lane放到fiber.lanes  
  34.     workInProgress.lanes = newLanes
  35.   }  

只處理優先級足夠的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.   

  1.                                root(pendingLanes: 0b01110)  
  2.                                    |  
  3. 1                                  App  
  4.                                    |  
  5.                                    |  
  6. 2 compeleteUnitOfWork-----------> div (childLanes: 0b01110)  
  7.                                    /  
  8.                                   /  
  9. 3                              <List/> ---------> <Table/> --------> p  
  10.                           (lanes: 0b00010)   (lanes: 0b00100)  
  11.                        (childLanes: 0b01000)       /  
  12.                                /                   /  
  13.                               /                   /  
  14. 4                            p                   ul  
  15.                                                 /  
  16.                                                /  
  17.                                               li ------> li 

在每一次往上循環的時候,都會調用resetChildLanes,目的是將fiber.childLanes層層收集。 

  1. function completeUnitOfWork(unitOfWork: Fiber): void {  
  2.   // 已經結束beginWork階段的fiber節點被稱為completedWork  
  3.   let completedWork = unitOfWork 
  4.   do {  
  5.     // 向上一直循環到root的過程  
  6.     ...  
  7.     // fiber節點的.flags上沒有Incomplete,說明是正常完成了工作  
  8.     if ((completedWork.flags & Incomplete) === NoFlags) {  
  9.       ...  
  10.       // 調用resetChildLanes去收集lanes  
  11.       resetChildLanes(completedWork);  
  12.       ...  
  13.     } else {/*...*/}  
  14.     ...  
  15.   } while (completedWork !== null);  
  16.   ...  

resetChildLanes中只收集當前正在complete的fiber節點的子節點和兄弟節點的lanes以及childLanes: 

  1. function resetChildLanes(completedWork: Fiber) {  
  2.   ...  
  3.   let newChildLanes = NoLanes 
  4.   if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {  
  5.     // profile相關,無需關注  
  6.   } else {  
  7.     // 循環子節點和兄弟節點,收集lanes  
  8.     let child = completedWork.child;  
  9.     while (child !== null) {  
  10.       // 收集過程  
  11.       newChildLanes = mergeLanes 
  12.         newChildLanes,  
  13.         mergeLanes(child.lanes, child.childLanes),  
  14.       );  
  15.       childchild = child.sibling;  
  16.     }  
  17.   }  
  18.   // 將收集到的lanes放到該fiber節點的childLanes中  
  19.   completedWork.childLanes = newChildLanes 

最后將這些收集到的childLanes放到root.pendingLanes的過程,是發生在本次更新的commit階段中,因為render階段的渲染優先級來自root.pendingLanes,不能隨意地修改它。所以要在render階段之后的commit階段去修改。我們看一下commitRootImpl中這個過程的實現: 

  1. function commitRootImpl(root, renderPriorityLevel) {  
  2.   // 將收集到的childLanes,連同root自己的lanes,一并賦值給remainingLanes  
  3.   let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);  
  4.   // markRootFinished中會將remainingLanes賦值給remainingLanes  
  5.   markRootFinished(root, remainingLanes);  
  6.   ...  

重新發起調度

至此,我們將低優先級任務的lane重新收集到了root.pendingLanes中,這時只需要再發起一次調度就可以了,通過在commit階段再次調用ensureRootIsScheduled去實現,這樣就又會走一遍調度的流程,低優先級任務被執行。 

  1. function commitRootImpl(root, renderPriorityLevel) {  
  2.   // 將收集到的childLanes,連同root自己的lanes,一并賦值給remainingLanes  
  3.   let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);  
  4.   // markRootFinished中會將remainingLanes賦值給remainingLanes  
  5.   markRootFinished(root, remainingLanes);  
  6.   ...  
  7.   // 在每次所有更新完成的時候都會調用這個ensureRootIsScheduled  
  8.   // 以保證root上任何的pendingLanes都能被處理  
  9.   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提升用戶體驗的決心。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2021-02-02 11:02:20

React任務饑餓行為優先級任務

2025-07-15 03:00:00

2022-06-13 10:24:47

宏任務微任務前端

2025-06-30 07:00:00

JavaScript開發線程

2015-05-20 16:51:35

谷歌云計算方案低優先級

2021-04-06 10:45:18

React前端優先級

2018-08-01 10:10:12

WindowsWindows10DPC

2021-07-29 06:56:35

前端事件循環

2024-05-11 08:31:20

中斷機制插隊機制React

2021-04-09 16:39:41

鴻蒙HarmonyOS應用

2025-10-17 09:24:51

2024-03-11 07:46:40

React優先級隊列二叉堆

2023-01-05 08:48:57

技術管理排優先級

2023-11-03 08:22:09

Android系統算法

2016-12-05 16:09:08

Windows 10PowerShell任務

2021-06-16 07:40:46

Linux運維Linux系統

2009-03-12 18:35:24

Windows 7任務欄

2009-04-27 16:09:32

Windows 7微軟操作系統

2010-09-01 14:10:36

CSS優先級

2012-08-14 09:38:29

WAN優化
點贊
收藏

51CTO技術棧公眾號

中文字幕在线视频精品| 成人激情黄色小说| 综合分类小说区另类春色亚洲小说欧美| 精品国内亚洲在观看18黄| 韩国一区二区三区美女美女秀| 实拍女处破www免费看| gogo在线观看| 国产资源在线看| 残酷重口调教一区二区| 亚洲午夜电影在线| 国产日本欧美一区二区三区| 亚洲av无码一区二区三区观看| 精品176二区| 日韩av不卡在线观看| 日韩国产激情在线| 欧美乱大交xxxxx潮喷l头像| 国产免费福利视频| 色综合狠狠操| 欧美日本一道本在线视频| 日本一区二区在线视频观看| 日本网站在线播放| 精品一区二区三区中文字幕在线 | 大地资源二中文在线影视观看| 菠萝蜜视频国产在线播放| 久久综合视频网| 91成人精品网站| 午夜福利三级理论电影| 香蕉久久aⅴ一区二区三区| 国产麻豆视频一区| www.欧美三级电影.com| 欧美日韩理论片| 日韩精品卡一| 国产精品乱人伦中文| 国产精品爱久久久久久久| 无码一区二区三区在线| a屁视频一区二区三区四区| 国产欧美日韩一区二区三区在线观看| 国产经典一区二区| 久久精品国产亚洲AV成人婷婷| 国产精品一区二区av影院萌芽| 久久综合狠狠综合| 国产色综合一区二区三区| 欧美日韩乱国产| 蜜臀av免费一区二区三区| 色欧美日韩亚洲| 日本成人三级| 亚洲欧美综合在线观看| 首页国产欧美日韩丝袜| 久久亚洲国产成人| 久草视频福利在线| 欧美精品资源| 一区二区三区美女| 老牛影视免费一区二区| 在线观看国产黄| 狠狠干综合网| 国产亚洲精品综合一区91| 午夜免费看毛片| 91九色在线播放| 国产日韩欧美制服另类| 亚洲一区二区三区视频| 久久国产视频精品| 国产韩日影视精品| 亚洲精品黄网在线观看| 2025韩国理伦片在线观看| 1769免费视频在线观看| 91在线观看一区二区| 国产精品视频精品视频| 日本熟妇一区二区| 999国产精品999久久久久久| 亚洲精品国产欧美| 日韩av一二区| 精品国产一区二区三区| 亚洲精品一区二区三区99| 久久撸在线视频| 涩涩涩在线视频| 亚洲男人电影天堂| 日本一区二区三区免费看| 二人午夜免费观看在线视频| 福利一区二区在线| 国产精品综合网站| 天堂а√在线中文在线新版 | 国内自拍欧美激情| 秋霞网一区二区三区| 欧美福利在线播放网址导航| 欧美精品久久久久久久多人混战| 精品这里只有精品| caopon在线免费视频| 一卡二卡三卡日韩欧美| 日本www在线视频| 性xxxfreexxxx性欧美| 午夜精品123| 大胆欧美熟妇xx| 日韩免费啪啪| 久久女同精品一区二区| 一本色道久久99精品综合| 日本大片在线观看| 成人app下载| 神马影院我不卡午夜| 日韩av成人| 日韩码欧中文字| 蜜桃网站成人| 欧美jizz18性欧美| 中文字幕一区二区三| 日本在线观看一区| 午夜激情小视频| 国产精品乱码一区二区三区软件| 男人天堂a在线| 日韩精品亚洲人成在线观看| 欧美在线观看视频在线| 日韩福利视频在线| 毛片无码国产| 欧美大片国产精品| 成人欧美精品一区二区| 欧美视频免费| 欧美精品久久久久久久久久| 九九热精品在线观看| 自拍偷拍欧美专区| 欧美丰满少妇xxxx| 久久一级黄色片| 亚洲精品婷婷| 日本成熟性欧美| 亚洲成人av影片| 日韩中文字幕91| 欧美性做爰毛片| 免费看污视频的网站| 日本成人超碰在线观看| 好吊色欧美一区二区三区视频| 91蜜桃在线视频| 欧美久久一二区| 一区二区三区在线观看免费视频| 日产精品一区二区| 琪琪第一精品导航| 天天干天天舔天天射| 久久久久久免费网| 亚洲一区二区高清视频| 91看片一区| 51精品视频一区二区三区| 超碰在线超碰在线| 中文字幕视频精品一区二区三区| 亚洲激情视频在线| 久久久久香蕉视频| 国产成人综合视频| 麻豆成人小视频| 老司机深夜福利在线观看| 亚洲а∨天堂久久精品9966| 国产亚洲成人av| 丁香婷婷综合网| 日韩成人在线资源| 亚洲一区二区三区四区| 亚洲午夜精品久久久久久久久久久久| 人人草在线观看| 国产日韩高清在线| 亚洲xxx在线观看| 亚洲精品中文字幕乱码| 亚洲tv在线观看| av官网在线播放| 日韩欧美成人一区| 日本午夜小视频| 91美女在线观看| 好色先生视频污| 精品丝袜在线| 91麻豆精品国产91久久久久久久久| 日本人亚洲人jjzzjjz| 美女网站色91| 国产精品二区三区| 黄色大片在线免费观看| 在线精品亚洲一区二区不卡| 亚洲国产精品狼友在线观看| 在线精品一区二区| 亚洲自拍欧美另类| 白白色在线观看| 欧美疯狂性受xxxxx喷水图片| 91嫩草丨国产丨精品| 噜噜爱69成人精品| 动漫一区二区在线| 中文字幕在线免费观看视频| 欧美一区二区三区电影| 欧美成人国产精品一区二区| 蜜桃精品视频在线| 日韩精品一区二区在线视频| 老司机在线精品视频| 插插插亚洲综合网| 欧美熟妇乱码在线一区| 亚洲你懂的在线视频| 蜜臀av粉嫩av懂色av| 欧美aaaaaa午夜精品| www.一区二区.com| 电影中文字幕一区二区| 中文字幕久久久av一区| 依依成人综合网| 成人免费在线播放视频| 亚洲自拍偷拍精品| 久久国产免费看| 狠狠色综合网站久久久久久久| 性欧美超级视频| 亚洲精品中文字幕av| 国产污片在线观看| 国产91精品一区二区麻豆网站 | 亚洲成在人线av| 欧美成人一区二区三区高清| 蜜桃视频免费观看一区| 久久综合久久网| 欧美成人精品一区二区三区在线看| 国产精品一区二区三区在线观| av在线播放观看| 亚洲免费视频一区二区| 97久久久久久久| 亚洲乱码日产精品bd| 欧美 日韩 成人| 成a人片亚洲日本久久| 视频免费1区二区三区 | 一本久道久久久| 另类专区欧美制服同性| 亚洲欧美日韩成人在线| 日韩三级电影网址| xxxx日本少妇| 国产精品一区二区x88av| 久久久久久久久久福利| 国产亚洲电影| 国产精品久久久久久久午夜| 2021中文字幕在线| 精品国产拍在线观看| 都市激情一区| 国产视频欧美视频| 日本加勒比一区| 欧美mv日韩mv| 亚洲最大成人综合网| 99久久99久久精品免费看蜜桃| 中文字幕第10页| 国产一区二区在线视频| 成人毛片100部免费看| 日韩精品首页| 日韩中文一区| 红桃视频在线观看一区二区| 欧美高清视频一区| 欧美亚洲福利| 欧美乱妇高清无乱码| 欧美图片自拍偷拍| 综合久久伊人| 欧美美女18p| 国产网友自拍视频导航网站在线观看| 最近2019年日本中文免费字幕 | 国产精品亚洲视频| 国产5g成人5g天天爽| 精品在线播放免费| 在线观看免费不卡av| 久久se精品一区精品二区| 伊人影院综合在线| 国产综合久久久久久鬼色| 在线免费看污网站| 国产精品一色哟哟哟| 色姑娘综合天天| 成人免费毛片嘿嘿连载视频| 在线天堂www在线国语对白| 青草国产精品久久久久久| 国产成人精品无码播放| 人人狠狠综合久久亚洲| 最新天堂在线视频| 国产成a人无v码亚洲福利| 中国黄色片视频| 久久青草国产手机看片福利盒子| 综合国产在线观看| 香蕉视频成人在线| 亚洲色图在线观看| 高h调教冰块play男男双性文| 色婷婷av一区二区| 中文字幕日韩三级| 精品久久久久久中文字幕大豆网| 国产又黄又粗又猛又爽的| 91玉足脚交白嫩脚丫在线播放| 日本黄色特级片| 国产成a人亚洲精| 在线 丝袜 欧美 日韩 制服| 中文字幕成人在线观看| 日韩片在线观看| 中文字幕av在线一区二区三区| 国产免费久久久久| 国产精品久久久久毛片软件| 日韩一级片av| 日韩欧美在线视频观看| 黄色一级免费视频| 好吊成人免视频| 日操夜操天天操| 欧美午夜精品久久久久久孕妇| 99国产精品久久久久99打野战| 亚洲激情成人网| 国产素人视频在线观看| 欧美在线一级视频| 成人污版视频| 久久综合久久综合这里只有精品| 99视频精品全部免费在线视频| 国产毛片视频网站| 久久99精品国产麻豆婷婷| 在线精品一区二区三区| 日韩毛片在线免费观看| 日日夜夜狠狠操| 精品对白一区国产伦| 欧美高清视频| 日本高清视频一区| 日韩不卡在线视频| 91精品国产99久久久久久红楼| 欧美91在线|欧美| 国产一区二区在线免费| 久本草在线中文字幕亚洲| 中文字幕中文字幕在线中一区高清 | 欧美三级一区二区| 日韩 国产 欧美| 精品日韩欧美一区二区| 91精品大全| 日韩天堂在线视频| 蜜桃av在线| 成人黄视频免费| 成人爽a毛片| 久久国产精品亚洲va麻豆| 亚洲午夜久久| 亚洲 日韩 国产第一区| 一区二区国产在线观看| 亚洲妇女无套内射精| 成人晚上爱看视频| 神马午夜精品91| 欧美午夜精品久久久| 亚洲人妻一区二区| 久久躁日日躁aaaaxxxx| 成人午夜亚洲| 日韩福利视频| 欧美一级网站| 国产又黄又猛又粗又爽的视频| 成人av高清在线| 久久久精品视频在线| 91精品黄色片免费大全| 秋霞a级毛片在线看| 国产精品普通话| 不卡日本视频| 国产精品视频分类| 欧美国产精品一区| 欧美男人天堂网| 欧美一级欧美三级在线观看| www视频在线观看免费| 日韩中文在线中文网三级| 69堂免费精品视频在线播放| 欧美日韩国产高清视频| 美女国产精品| 黄色aaa视频| 欧洲一区在线观看| 国产精品麻豆一区二区三区| 国产精品久久久久久久久久新婚 | 粉嫩av一区二区| 欧美日韩在线高清| 免费在线日韩av| 99久久人妻无码精品系列| 色婷婷一区二区三区四区| av在线资源观看| 亚洲男人天堂2024| 久久91导航| 亚洲一区二区三区四区中文| 精品亚洲欧美一区| 国产精品边吃奶边做爽| 色香色香欲天天天影视综合网| 免费在线看v| 欧美—级高清免费播放| 免费福利视频一区| 丁香啪啪综合成人亚洲| 中文乱码免费一区二区| 国产口爆吞精一区二区| 亚洲社区在线观看| 国产伊人久久| 老司机午夜网站| 蜜桃av噜噜一区二区三区小说| 国产精品免费在线视频| 色呦呦国产精品| 乱人伦中文视频在线| 国产精品高清一区二区三区| 亚洲综合不卡| 久久久久久久久久97| 亚洲国产日韩欧美在线动漫| 欧洲成人一区| 嫩草影院中文字幕| 国产色一区二区| 成人h动漫精品一区二区无码| 日韩中文字幕视频在线观看| 伊人精品久久| mm1313亚洲国产精品无码试看| 亚洲视频在线一区| 天堂av中文在线资源库| 欧美风情在线观看| 亚洲美女久久| 精品国产鲁一鲁一区二区三区| 午夜精品爽啪视频| 麻豆影院在线观看| 久久久久久久久一区| 国内精品免费在线观看| av图片在线观看| 国产欧美一区二区精品性| aa片在线观看视频在线播放| 欧美视频在线一区二区三区 | 99久久精品国产亚洲精品 | www.久久久久久久久久| 国产成人在线播放| 极品少妇一区二区三区| 国产精品国产三级国产传播|