最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React狀態(tài)更新的優(yōu)先級機制源碼解析

 更新時間:2022年11月08日 14:59:38   作者:goClient1992  
這篇文章主要為大家介紹了React狀態(tài)更新的優(yōu)先級機制源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

為什么需要優(yōu)先級

優(yōu)先級機制最終目的是為了實現(xiàn)高優(yōu)先級任務(wù)優(yōu)先執(zhí)行,低優(yōu)先級任務(wù)延后執(zhí)行。

實現(xiàn)這一目的的本質(zhì)就是在低優(yōu)先級任務(wù)執(zhí)行時,有更高優(yōu)先級任務(wù)進來的話,可以打斷低優(yōu)先級任務(wù)的執(zhí)行。

同步模式下的react運行時

我們知道在同步模式下,從 setState 到 虛擬DOM遍歷,再到真實DOM更新,整個過程都是同步執(zhí)行且無法被中斷的,這樣可能就會出現(xiàn)一個問題 —— 用戶事件觸發(fā)的更新被阻塞。

什么是用戶事件觸發(fā)的更新被阻塞?如果 React 正在進行更新任務(wù),此時用戶觸發(fā)了交互事件,且在事件回調(diào)中執(zhí)行了 setState,在同步模式下,這個更新任務(wù)需要 等待 當(dāng)前正在更新的任務(wù)完成之后,才會被執(zhí)行。假如當(dāng)前 React 正在進行的更新任務(wù)耗時比較久,用戶事件觸發(fā)的更新任務(wù)不能及時被執(zhí)行,造成下個更新任務(wù)被阻塞,從而形成了卡頓。

這時候,我們就希望能夠及時響應(yīng)用戶觸發(fā)的事件,優(yōu)先執(zhí)行用戶事件觸發(fā)的更新任務(wù),也就是我們說的異步模式

我們可以比較一下,同步模式下和異步模式(優(yōu)先級機制)下更新任務(wù)執(zhí)行的差異

import React from "react";
import "./styles.css";
export default class extends React.Component {
  constructor() {
    super();
    this.state = {
      list: new Array(10000).fill(1),
    };
    this.domRef = null;
  }
  componentDidMount() {
    setTimeout(() => {
      console.log("setTimeout 準(zhǔn)備更新", performance.now());
      this.setState(
        {
          list: new Array(10000).fill(Math.random() * 10000),
          updateLanes: 16
        },
        () => {
          console.log("setTimeout 更新完畢", performance.now());
        }
      );
    }, 100);
    setTimeout(() => {
      this.domRef.click();
    }, 150);
  }
  render() {
    const { list } = this.state;
    return (
      <div
        ref={(v) => (this.domRef = v)}        className="App"        onClick={() => {          console.log("click 準(zhǔn)備更新", performance.now());          this.setState(            { list: new Array(10000).fill(2), updateLanes: 1 },            () => {              console.log("click 更新完畢", performance.now());            }          );        }}      >        {list.map((i, index) => (          <h2 key={i + +index}>Hello {i}</h2>
        ))}      </div>
    );
  }
}

click事件 觸發(fā)的更新,會比 setTimeout 觸發(fā)的更新更優(yōu)先執(zhí)行,做到了及時響應(yīng)用戶事件,打斷 setTimeout 更新任務(wù)(低優(yōu)先級任務(wù))的執(zhí)行。

如何運用優(yōu)先級機制優(yōu)化react運行時

為了解決同步模式渲染下的缺陷,我們希望能夠?qū)?react 做出下面這些優(yōu)化

  • 確定不同場景下所觸發(fā)更新的優(yōu)先級,以便我們可以決定優(yōu)先執(zhí)行哪些任務(wù)
  • 若有更高優(yōu)先級的任務(wù)進來,我們需要打斷當(dāng)前進行的任務(wù),然后執(zhí)行這個高優(yōu)先級任務(wù)
  • 確保低優(yōu)先級任務(wù)不會被一直打斷,在一定時間后能夠被升級為最高優(yōu)先級的任務(wù)

確定不同場景下的調(diào)度優(yōu)先級

看過 react 源碼的小伙伴可能都會有一個疑惑,為什么源碼里面有那么多優(yōu)先級相關(guān)的單詞??怎么區(qū)分他們呢?

其實在 react 中主要分為兩類優(yōu)先級,scheduler 優(yōu)先級和 lane 優(yōu)先級,lane優(yōu)先級下面又派生出 event 優(yōu)先級

  • lane 優(yōu)先級:主要用于任務(wù)調(diào)度前,對當(dāng)前正在進行的任務(wù)和被調(diào)度任務(wù)做一個優(yōu)先級校驗,判斷是否需要打斷當(dāng)前正在進行的任務(wù)
  • event 優(yōu)先級:本質(zhì)上也是lane優(yōu)先級,lane優(yōu)先級是通用的,event優(yōu)先級更多是結(jié)合瀏覽器原生事件,對lane優(yōu)先級做了分類和映射
  • scheduler 優(yōu)先級:主要用在時間分片中任務(wù)過期時間的計算

lane優(yōu)先級

可以用賽道的概念去理解lane優(yōu)先級,lane優(yōu)先級有31個,我們可以用31位的二進制值去表示,值的每一位代表一條賽道對應(yīng)一個lane優(yōu)先級,賽道位置越靠前,優(yōu)先級越高

優(yōu)先級十進制值二進制值賽道位置
NoLane000000000000000000000000000000000
SyncLane100000000000000000000000000000010
InputContinuousHydrationLane200000000000000000000000000000101
InputContinuousLane400000000000000000000000000001002
DefaultHydrationLane800000000000000000000000000010003
DefaultLane1600000000000000000000000000100004
TransitionHydrationLane3200000000000000000000000001000005
TransitionLane16400000000000000000000000010000006
TransitionLane212800000000000000000000000100000007
TransitionLane325600000000000000000000001000000008
TransitionLane451200000000000000000000010000000009
TransitionLane51024000000000000000000001000000000010
TransitionLane2048000000000000000000010000000000011
TransitionLane74096000000000000000000100000000000012
TransitionLane88192000000000000000001000000000000013
TransitionLane916384000000000000000010000000000000014
TransitionLane1032768000000000000000100000000000000015
TransitionLane1165536000000000000001000000000000000016
TransitionLane12131072000000000000010000000000000000017
TransitionLane13262144000000000000100000000000000000018
TransitionLane14524288000000000001000000000000000000019
TransitionLane151048576000000000010000000000000000000020
TransitionLane162097152000000000100000000000000000000021
RetryLane14194304000000001000000000000000000000022
RetryLane28388608000000010000000000000000000000023
RetryLane316777216000000100000000000000000000000024
RetryLane433554432000001000000000000000000000000025
RetryLane567108864000010000000000000000000000000026
SelectiveHydrationLane134217728000100000000000000000000000000027
IdleHydrationLane268435456001000000000000000000000000000028
IdleLane536870912010000000000000000000000000000029
OffscreenLane1073741824100000000000000000000000000000030

event優(yōu)先級

EventPriority Lane數(shù)值
DiscreteEventPriority離散事件。click、keydown、focusin等,事件的觸發(fā)不是連續(xù),可以做到快速響應(yīng)SyncLane1
ContinuousEventPriority連續(xù)事件。drag、scroll、mouseover等,事件的是連續(xù)觸發(fā)的,快速響應(yīng)可能會阻塞渲染,優(yōu)先級較離散事件低InputContinuousLane4
DefaultEventPriority默認的事件優(yōu)先級DefaultLane16
IdleEventPriority空閑的優(yōu)先級IdleLane536870912

scheduler優(yōu)先級

SchedulerPriorityEventPriority大于>17.0.2小于>17.0.2
ImmediatePriorityDiscreteEventPriority199
UserblockingPriorityUserblocking298
NormalPriorityDefaultEventPriority397
LowPriorityDefaultEventPriority496
IdlePriorityIdleEventPriority595
NoPriority 090

優(yōu)先級間的轉(zhuǎn)換

lane優(yōu)先級 轉(zhuǎn) event優(yōu)先級(參考 lanesToEventPriority 函數(shù))

  • 轉(zhuǎn)換規(guī)則:以區(qū)間的形式根據(jù)傳入的lane返回對應(yīng)的 event 優(yōu)先級。比如傳入的優(yōu)先級不大于 Discrete 優(yōu)先級,就返回 Discrete 優(yōu)先級,以此類推

event優(yōu)先級 轉(zhuǎn) scheduler優(yōu)先級(參考 ensureRootIsScheduled 函數(shù))

  • 轉(zhuǎn)換規(guī)則:可以參考上面scheduler優(yōu)先級表

event優(yōu)先級 轉(zhuǎn) lane優(yōu)先級(參考 getEventPriority 函數(shù))

  • 轉(zhuǎn)換規(guī)則:對于非離散、連續(xù)的事件,會根據(jù)一定規(guī)則作轉(zhuǎn)換,具體課參考上面 event 優(yōu)先級表,

優(yōu)先級機制如何設(shè)計

說到優(yōu)先級機制,我們可能馬上能聯(lián)想到的是優(yōu)先級隊列,其最突出的特性是最高優(yōu)先級先出,react 的優(yōu)先級機制跟優(yōu)先級隊列類似,不過其利用了賽道的概念,配合位與運算豐富了隊列的功能,比起優(yōu)先級隊列,讀寫速度更快,更加容易理解

設(shè)計思路

  • 合并賽道:維護一個隊列,可以存儲被占用的賽道
  • 釋放賽道:根據(jù)優(yōu)先級釋放對應(yīng)被占用賽道
  • 找出最高優(yōu)先級賽道:獲取隊列中最高優(yōu)先級賽道
  • 快速定位賽道索引:根據(jù)優(yōu)先級獲取賽道在隊列中所在的位置
  • 判斷賽道是否被占用:根據(jù)傳入優(yōu)先級判斷該優(yōu)先級所在賽道是否被占用

合并賽道

場景

  • 比如當(dāng)前正在調(diào)度的任務(wù)優(yōu)先級是DefaultLane,用戶點擊觸發(fā)更新,有一個高優(yōu)先級的任務(wù)SyncLane產(chǎn)生,需要存儲這個任務(wù)所占用的賽道

運算過程

  • 運算方式:位或運算 - a | b
  • 運算結(jié)果:DefaultLane和SyncLane分別占用了第1條和第5條賽道

DefaultLane優(yōu)先級為16,SyncLane優(yōu)先級為1

16 | 1 = 17

17的二進制值為10001
16的二進制值為10000,1的二進制值為00001

釋放賽道

場景

  • SyncLane 任務(wù)執(zhí)行完,需要釋放占用的賽道

運算過程

  • 運算方式:位與+位非 - a & ~b
  • 運算結(jié)果:SyncLane賽道被釋放,只剩下DefaultLane賽道

17 & ~1 = 16
17的二進制值為10001

為什么用位非?
~1 = -2
2 的二進制是00010,-2的話符號位取反變?yōu)?0010
10001和10010進行位與運算得到10000,也就是十進制的16

找出最高優(yōu)先級賽道

場景

  • 當(dāng)前有 DefaultLane 和 SyncLane 兩個優(yōu)先級的任務(wù)占用賽道,在進入 ensureRootIsScheduled 方法后,我需要先調(diào)度優(yōu)先級最高的任務(wù),所以需要找出當(dāng)前優(yōu)先級最高的賽道

運算過程

  • 運算方式:位與+符號位取反 - a & -b
  • 運算結(jié)果:找到了最高優(yōu)先級的任務(wù)SyncLane,SyncLane任務(wù)為同步任務(wù),Scheduler將以同步優(yōu)先級調(diào)度當(dāng)前應(yīng)用根節(jié)點

17 & -17 = 1

17的二進制值為10001
-17的二進制值為00001
10001和00001進行位與運算得到1,也就是SyncLane

快速定位賽道索引

場景

  • 饑餓任務(wù)喚醒:在發(fā)起調(diào)度前,我們需要對隊列中的所有賽道進行一個判斷,判斷該賽道的任務(wù)是否過期,如果過期,就優(yōu)先執(zhí)行該過期任務(wù)。為此,需要維護一個長度為31的數(shù)組,數(shù)組的每個元素的下標(biāo)索引與31個優(yōu)先級賽道一一對應(yīng),數(shù)組中存儲的是任務(wù)的過期時間,在判斷時,我們希望能根據(jù)優(yōu)先級快速找到該優(yōu)先級在數(shù)組中對應(yīng)的位置。

運算過程

  • 運算方式:Math.clz32
  • 運算結(jié)果:找到了DefaultLane的索引位置為4,那就可以釋放應(yīng)用根節(jié)點上的eventTimes、expirationTimes,將其所在位置的值賦值為-1,然后執(zhí)行對應(yīng)的過期任務(wù)

// 找出 DefaultLane 賽道索引
31 - Math.clz32(16) = 4

16的二進制值為10000
索引4對應(yīng)的就是第五個賽道

Math.clz32是用來干什么的?

  • 獲取一個十進制數(shù)字對應(yīng)二進制值中開頭0的個數(shù)。
  • 所以用31減去 Math.clz32 的值就能得到該賽道的索引

判斷賽道是否被占用

異步模式下會存在高優(yōu)先級任務(wù)插隊的情況,此情況下 state 的計算方式會跟同步模式下**有些不同。

場景

我們 setState 之后并不是馬上就會更新 state,而是會根據(jù) setState 的內(nèi)容生成一個 Update 對象,這個對象包含了更新內(nèi)容、更新優(yōu)先級等屬性。

更新 state 這個動作是在 processUpdateQueue 函數(shù)里進行的,函數(shù)里面會判斷 Update 對象的優(yōu)先級所在賽道是否被占用,來決定是否在此輪任務(wù)中計算這個 Update 對象的 state

  • 如果被占用,代表 Update 對象優(yōu)先級和當(dāng)前正在進行的任務(wù)相等,可以根據(jù) Update 對象計算 state 并更新到 Fiber 節(jié)點的 memoizedState 屬性上
  • 如果未被占用,代表當(dāng)前正在進行的任務(wù)優(yōu)先級比這個 Update 對象優(yōu)先級高,相應(yīng)的這個低優(yōu)先級的 Update 對象將暫不被計算state,留到下一輪低優(yōu)先級任務(wù)被重啟時再進行計算

運算過程

  • 運算方式:位與 (renderLanes & updateLanes) == updateLanes
  • 運算結(jié)果:0代表當(dāng)前調(diào)度優(yōu)先級高于某個Update對象優(yōu)先級

運算公式
(1 & 16) == 16

1的二進制值為00001
16的二進制值為10000 
00001和10000進行位與運算得到0

如何將優(yōu)先級機制融入React運行時

生成一個更新任務(wù)

生成任務(wù)的流程其實非常簡單,入口就在我們常用的 setState 函數(shù),先上圖

setState 函數(shù)內(nèi)部執(zhí)行的就是 enqueueUpdate 函數(shù),而 enqueueUpdate 函數(shù)的工作主要分為4步:

  • 獲取本次更新的優(yōu)先級。
  • 創(chuàng)建 Update 對象
  • 將本次更新優(yōu)先級關(guān)聯(lián)到當(dāng)前Fiber節(jié)點、父級節(jié)點和應(yīng)用根節(jié)點
  • 發(fā)起 ensureRootIsScheduled 調(diào)度。

步驟一:獲取本次更新的優(yōu)先級

步驟一的工作是調(diào)用 requestUpdateLane 函數(shù)拿到此次更新任務(wù)的優(yōu)先級

如果當(dāng)前為非 concurrent 模式

  • 當(dāng)前不在 render 階段。返回 syncLane
  • 當(dāng)前正在 render 階段。返回 workInProgressRootRenderLanes 中最高的優(yōu)先級(這里就用到上面的優(yōu)先級運算機制,找出最高優(yōu)先級賽道

如果當(dāng)前為 concurrent 模式

  • 需要執(zhí)行延遲任務(wù)的話,比如 Suspend、useTransitionuseDefferedValue 等特性。在 transition 類型的優(yōu)先級中尋找空閑的賽道。transition類型的賽道有 16 條,從第 1 條到第 16 條,當(dāng)?shù)竭_第 16 條賽道后,下一次 transition 類型的任務(wù)會回到第 1 條賽道,如此往復(fù)。
  • 執(zhí)行 getCurrentUpdatePriority 函數(shù)。獲取當(dāng)前更新優(yōu)先級。如果不為 NoLane 就返回
  • 執(zhí)行 getCurrentEventPriority 函數(shù)。返回當(dāng)前的事件優(yōu)先級。如果沒有事件產(chǎn)生,返回 DefaultEventPriority

總的來說,requestUpdateLane 函數(shù)的優(yōu)先級選取判斷順序如下:

SyncLane  >>  TransitionLane  >>  UpdateLane  >>  EventLane

估計有很多小伙伴都會很困惑一個問題,為什么會有這么多獲取優(yōu)先級的函數(shù),這里我整理了一下其他函數(shù)的職責(zé)

步驟二:創(chuàng)建 Update 對象

這里的代碼量不多,其實就是將 setState 的參數(shù)用一個對象封裝起來,留給 render 階段用

function createUpdate(eventTime, lane) {
  var update = {
    eventTime: eventTime,
    lane: lane,
    tag: UpdateState,
    payload: null,
    callback: null,
    next: null
  };
  return update;
}

步驟三:關(guān)聯(lián)優(yōu)先級

在這里先解釋兩個概念,一個是 HostRoot,一個是 FiberRootNode

  • HostRoot:就是 ReactDOM.render 的第一個參數(shù),組件樹的根節(jié)點。HostRoot可能會存在多個,因為 ReactDOM.render 可以多次調(diào)用
  • FiberRootNode:react 的應(yīng)用根節(jié)點,每個頁面只有一個 react 的應(yīng)用根節(jié)點??梢詮?HostRoot 節(jié)點的 stateNode 屬性訪問

這里關(guān)聯(lián)優(yōu)先級主要執(zhí)行了兩個函數(shù)

markUpdateLaneFromFiberToRoot。該函數(shù)主要做了兩個事情

  • 將優(yōu)先級合并到當(dāng)前 Fiber 節(jié)點的 lanes 屬性中
  • 將優(yōu)先級合并到父級節(jié)點的 childLanes 屬性中(告訴父節(jié)點他的子節(jié)點有多少條賽道要跑) 但因為函數(shù)傳入的 Fiber 節(jié)點是 HostRoot,也就是 ReactDOM.render 的根節(jié)點,也就是說沒有父節(jié)點了,所以第二件事情沒有做

markRootUpdated。該函數(shù)也是主要做了兩個事情

  • 將待調(diào)度任務(wù)優(yōu)先級合并到當(dāng)前 react 應(yīng)用根節(jié)點上
  • 計算當(dāng)前任務(wù)優(yōu)先級賽道占用的開始時間(eventTime)

由此可見,react 的優(yōu)先級機制并不獨立運行在每一個組件節(jié)點里面,而是依賴一個全局的 react 應(yīng)用根節(jié)點去控制下面多個組件樹的任務(wù)調(diào)度

優(yōu)先級關(guān)聯(lián)到這些Fiber節(jié)點有什么用?

先說說他們的區(qū)別

  • lanes:只存在非 react 應(yīng)用根節(jié)點上,記錄當(dāng)前 Fiber 節(jié)點的 lane 優(yōu)先級
  • childLanes:只存在非 react 應(yīng)用根節(jié)點上,記錄當(dāng)前 Fiber 節(jié)點下的所有子 Fiber 節(jié)點的 lane 優(yōu)先級
  • pendingLanes:只存在 react 應(yīng)用根節(jié)點上,記錄的是所有 HostRoot 的 lane 優(yōu)先級

具體應(yīng)用場景

  • 釋放賽道。上面說的優(yōu)先級運算機制提到了任務(wù)執(zhí)行完畢會釋放賽道,具體來說是在 commit 階段結(jié)束之后釋放被占用的優(yōu)先級,也就是 markRootFinished 函數(shù)。
  • 判斷賽道是否被占用。在 render 階段的 beginWork 流程里面,會有很多判斷 childLanes 是否被占用的判斷

步驟四:發(fā)起調(diào)度

調(diào)度里面最關(guān)鍵的一步,就是 ensureRootIsScheduled 函數(shù)的調(diào)用,該函數(shù)的邏輯就是由下面兩大部分構(gòu)成,高優(yōu)先級任務(wù)打斷低優(yōu)先級任務(wù)饑餓任務(wù)問題

高優(yōu)先級任務(wù)打斷低優(yōu)先級任務(wù)

該部分流程可以分為三部曲

  • cancelCallback
  • pop(taskQueue)
  • 低優(yōu)先級任務(wù)重啟

cancelCallback

var existingCallbackNode = root.callbackNode;
var existingCallbackPriority = root.callbackPriority;
var newCallbackPriority = getHighestPriorityLane(nextLanes);
if (existingCallbackPriority === newCallbackPriority) {
    ...
    return;
}
if (existingCallbackNode != null) {
    cancelCallback(existingCallbackNode);
}
newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
);
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;

上面是 ensureRootIsScheduled 函數(shù)的一些代碼片段,先對變量做解釋

existingCallbackNode:當(dāng)前 render 階段正在進行的任務(wù)

existingCallbackPriority:當(dāng)前 render 階段正在進行的任務(wù)優(yōu)先級

newCallbackPriority:此次調(diào)度優(yōu)先級

這里會判斷 existingCallbackPrioritynewCallbackPriority 兩個優(yōu)先級是否相等,如果相等,此次更新合并到當(dāng)前正在進行的任務(wù)中。如果不相等,代表此次更新任務(wù)的優(yōu)先級更高,需要打斷當(dāng)前正在進行的任務(wù)

如何打斷任務(wù)?

  • 關(guān)鍵函數(shù) cancelCallback(existingCallbackNode),cancelCallback 函數(shù)就是將 root.callbackNode 賦值為null
  • performConcurrentWorkOnRoot 函數(shù)會先把 root.callbackNode 緩存起來,在函數(shù)末尾會再判斷 root.callbackNode 和開始緩存起來的值是否一樣,如果不一樣,就代表 root.callbackNode 被賦值為null了,有更高優(yōu)先級任務(wù)進來。
  • 此時 performConcurrentWorkOnRoot 返回值為null

下面是 performConcurrentWorkOnRoot 代碼片段

...
var originalCallbackNode = root.callbackNode;
...
// 函數(shù)末尾
if (root.callbackNode === originalCallbackNode) {
    return performConcurrentWorkOnRoot.bind(null, root);
}
return null;

由上面 ensureRootIsScheduled 的代碼片段可以知道,performConcurrentWorkOnRoot 函數(shù)是被 scheduleCallback 函數(shù)調(diào)度的,具體返回后的邏輯需要到 Scheduler 模塊去找

pop(taskQueue)

var callback = currentTask.callback;
if (typeof callback === 'function') {
  ...
} else {
  pop(taskQueue);
}

上面是 Scheduler 模塊里面 workLoop 函數(shù)的代碼片段,currentTask.callback 就是 scheduleCallback 的第二個參數(shù),也就是performConcurrentWorkOnRoot 函數(shù)

承接上個主題,如果 performConcurrentWorkOnRoot 函數(shù)返回了null,workLoop 內(nèi)部就會執(zhí)行 pop(taskQueue),將當(dāng)前的任務(wù)從 taskQueue 中彈出。

低優(yōu)先級任務(wù)重啟

上一步中說道一個低優(yōu)先級任務(wù)從 taskQueue 中被彈出。那高優(yōu)先級任務(wù)執(zhí)行完畢之后,如何重啟回之前的低優(yōu)先級任務(wù)呢?

關(guān)鍵是在 commitRootImpl 函數(shù)

var remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
...
ensureRootIsScheduled(root, now());

markRootFinished 函數(shù)剛剛上面說了是釋放已完成任務(wù)所占用的賽道,那也就是說未完成任務(wù)依然會占用其賽道,所以我們可以重新調(diào)用 ensureRootIsScheduled 發(fā)起一次新的調(diào)度,去重啟低優(yōu)先級任務(wù)的執(zhí)行。我們可以看下重啟部分的判斷

var nextLanes = getNextLanes(
    root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
// 如果 nextLanes 為 NoLanes,就證明所有任務(wù)都執(zhí)行完畢了
if (nextLanes === NoLanes) {
    ...
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    // 只要 nextLanes 為 NoLanes,就可以結(jié)束調(diào)度了
    return;
}
// 如果 nextLanes 不為 NoLanes,就代表還有任務(wù)未執(zhí)行完,也就是那些被打斷的低優(yōu)先級任務(wù)
...

饑餓任務(wù)問題

上面說到,在高優(yōu)先級任務(wù)執(zhí)行完畢之后,低優(yōu)先級任務(wù)就會被重啟,但假設(shè)如果持續(xù)有高優(yōu)先級任務(wù)持續(xù)進來,我的低優(yōu)先級任務(wù)豈不是沒有重啟之日?

所以 react 為了處理解決饑餓任務(wù)問題,react 在 ensureRootIsScheduled 函數(shù)開始的時候做了以下處理:(參考markStarvedLanesAsExpired函數(shù))

var lanes = pendingLanes;
while (lanes > 0) {
    var index = pickArbitraryLaneIndex(lanes);
    var lane = 1 << index;
    var expirationTime = expirationTimes[index];
    if (expirationTime === NoTimestamp) {
      if ((lane & suspendedLanes) === NoLanes || (lane & pingedLanes) !== NoLanes) {
        expirationTimes[index] = computeExpirationTime(lane, currentTime);
      }
    } else if (expirationTime <= currentTime) {
      root.expiredLanes |= lane;
    }
    lanes &= ~lane;
}
  • 遍歷31條賽道,判斷每條賽道的過期時間是否為 NoTimestamp,如果是,且該賽道存在待執(zhí)行的任務(wù),則為該賽道初始化過期時間
  • 如果該賽道已存在過期時間,且過期時間已經(jīng)小于當(dāng)前時間,則代表任務(wù)已過期,需要將當(dāng)前優(yōu)先級合并到 expiredLanes,這樣在下一輪 render 階段就會以同步優(yōu)先級調(diào)度當(dāng)前 HostRoot

可以參考 render 階段執(zhí)行的函數(shù) performConcurrentWorkOnRoot 中的代碼片段

var exitStatus = shouldTimeSlice(root, lanes) && ( !didTimeout) ? 
                    renderRootConcurrent(root, lanes) : 
                    renderRootSync(root, lanes);

可以看到只要 shouldTimeSlice 只要返回 false,就會執(zhí)行 renderRootSync,也就是以同步優(yōu)先級進入 render 階段。而 shouldTimeSlice 的邏輯也就是剛剛的 expiredLanes 屬性相關(guān)

function shouldTimeSlice(root, lanes) {
  // 如果 expiredLanes 里面有東西,代表有饑餓任務(wù)
  if ((lanes & root.expiredLanes) !== NoLanes) {
    return false;
  }
  var SyncDefaultLanes = InputContinuousHydrationLane | 
                          InputContinuousLane | 
                          DefaultHydrationLane | 
                          DefaultLane;
  return (lanes & SyncDefaultLanes) === NoLanes;
}

總結(jié)

react 的優(yōu)先級機制在源碼中并不是一個獨立的,解耦的模塊,而是涉及到了react整體運行的方方面面,最后回歸整理下優(yōu)先級機制在源碼中的使用,讓大家對優(yōu)先級機制有一個更加整體的認知。

  • 時間分片。涉及到任務(wù)打斷、根據(jù)優(yōu)先級計算分片時長
  • setState 生成 Update 對象。每個 Update 對象里面都有一個 lane 屬性,代表此次更新的優(yōu)先級
  • 高優(yōu)先級任務(wù)打斷低優(yōu)先級任務(wù)。每一次調(diào)度都會對正在進行任務(wù)和當(dāng)前任務(wù)最高優(yōu)先級做比較,如果不相等,就代表有高優(yōu)先級任務(wù)進來,需要打斷當(dāng)前正在的任務(wù)。
  • 低優(yōu)先級任務(wù)重啟。協(xié)調(diào) (reconcile) 的下一個階段是渲染 (renderer),也就是我們說的 commit 階段,在此階段末尾,會調(diào)用 ensureRootIsScheduled 發(fā)起一次新的調(diào)度,執(zhí)行尚未完成的低優(yōu)先級任務(wù)。
  • 饑餓任務(wù)喚醒。每次調(diào)度的開始,都會先檢查下有沒有過期任務(wù),如果有的話,下一次就會以同步優(yōu)先級進行 render 任務(wù)(reconcile),同步優(yōu)先級就是最高的優(yōu)先級,不會被打斷

以上就是React狀態(tài)更新的優(yōu)先級機制源碼解析的詳細內(nèi)容,更多關(guān)于React 狀態(tài)更新優(yōu)先級的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • react-three-fiber實現(xiàn)炫酷3D粒子效果首頁

    react-three-fiber實現(xiàn)炫酷3D粒子效果首頁

    這篇文章主要介紹了react-three-fiber實現(xiàn)3D粒子效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08
  • react父組件更改props子組件無法刷新原因及解決方法

    react父組件更改props子組件無法刷新原因及解決方法

    使用過vue的朋友都知道,vue父組件更改props的值,子組件是會刷新的,而react就未必,今天通過一個例子給大家介紹react父組件更改props子組件無法刷新原因,需要的朋友可以參考下
    2022-09-09
  • React Fragment 和空標(biāo)簽(<></>)用法及區(qū)別詳解

    React Fragment 和空標(biāo)簽(<></>)用法及區(qū)別詳解

    本文詳細介紹了React中的Fragment和空標(biāo)簽的使用,包括它們的區(qū)別、使用場景、性能考慮以及最佳實踐,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • react中路由跳轉(zhuǎn)及傳參的實現(xiàn)

    react中路由跳轉(zhuǎn)及傳參的實現(xiàn)

    本文主要介紹了react中路由跳轉(zhuǎn)及傳參的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • React高階組件使用教程詳解

    React高階組件使用教程詳解

    高階組件就是接受一個組件作為參數(shù)并返回一個新組件(功能增強的組件)的函數(shù)。這里需要注意高階組件是一個函數(shù),并不是組件,這一點一定要注意,本文給大家分享React 高階組件HOC使用小結(jié),一起看看吧
    2022-12-12
  • React事件處理過程中傳參的實現(xiàn)方法

    React事件處理過程中傳參的實現(xiàn)方法

    這篇文章主要介紹了React事件處理過程中傳參的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-10-10
  • React實現(xiàn)前端選區(qū)的示例代碼

    React實現(xiàn)前端選區(qū)的示例代碼

    本文主要介紹了React實現(xiàn)前端選區(qū)的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • React Native提供自動完成的下拉菜單的方法示例

    React Native提供自動完成的下拉菜單的方法示例

    這篇文章主要為大家介紹了React Native提供自動完成的下拉菜單的方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • ReactNative中使用Redux架構(gòu)總結(jié)

    ReactNative中使用Redux架構(gòu)總結(jié)

    本篇文章主要介紹了ReactNative中使用Redux架構(gòu)總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • 詳解使用React進行組件庫開發(fā)

    詳解使用React進行組件庫開發(fā)

    本篇文章主要介紹了詳解使用React進行組件庫開發(fā),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02

最新評論

江陵县| 昌宁县| 郑州市| 桓台县| 抚远县| 楚雄市| 龙海市| 烟台市| 屯昌县| 通道| 佛坪县| 老河口市| 上犹县| 湟中县| 金山区| 江安县| 鄂尔多斯市| 安仁县| 体育| 富阳市| 纳雍县| 读书| 怀仁县| 青龙| 晋中市| 保山市| 元谋县| 公主岭市| 宁都县| 永寿县| 陈巴尔虎旗| 溆浦县| 承德市| 丹江口市| 岫岩| 仁布县| 雷州市| 泰州市| 保靖县| 梁河县| 宁津县|