梳理React中的fiber架構

文章目錄

    • 產生背景
    • 核心概念
    • 工作原理
    • 工作流程
    • 優勢特點

產生背景

在React16之前使用的虛擬DOM是數組的形式,又因為React本身是應用級框架,狀態改變后并不能準確知道是哪個組件發生了改變,只能對整個應用進行diff協調,受限于虛擬DOM的結構,這個過程不能被打斷,于是就會出現JS占用主線程過多時間,導致頁面不能及時渲染而出現卡頓。
于是FaceBook團隊對內部機制進行重構,旨在解決 React 在處理復雜和大型應用時面臨的一些性能問題,尤其是渲染和更新界面的過程,fiber就誕生了。

核心概念

Fiber數據結構:Fiber本質上是一個JavaScript對象,代表React的一個工作單元,包含組件相關的信息,如類型、屬性、狀態等。

//本質上是一個對象結構
{type: 'div',  // 組件類型key: null,   // React keyprops: { ... }, // 輸入的propsstate: { ... }, // 組件的state (如果是class組件或帶有state的function組件)child: Fiber | null,  // 第一個子元素的Fibersibling: Fiber | null,  // 下一個兄弟元素的Fiberreturn: Fiber | null,  // 父元素的Fiber// ...其他屬性
}

Fiber樹:Fiber樹是React Fiber架構中的一個核心概念,它是一種特殊的鏈表數據結構,用于表示React組件樹的結構和狀態。Fiber樹的每個節點(Fiber節點)代表React中的一個工作單元,包含了組件的相關信息,如類型、屬性、狀態等,每個節點通過child、sibling和return字段構成一個鏈表結構,使React能夠遍歷組件樹并知道從哪里開始、繼續或停止工作,從而形成Fiber樹。

//FiberNode結構
function FiberNode(this: $FlowFixMe,tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode,
) {// 基本屬性this.tag = tag; // 描述此Fiber的啟動模式的值(LegacyRoot = 0; ConcurrentRoot = 1)this.key = key; // React keythis.elementType = null; // 描述React元素的類型。例如,對于JSX<App />,elementType是Appthis.type = null; // 組件類型this.stateNode = null; // 對于類組件,這是類的實例;對于DOM元素,它是對應的DOM節點。// Fiber鏈接this.return = null; // 指向父Fiberthis.child = null; // 指向第一個子Fiberthis.sibling = null; // 指向其兄弟Fiberthis.index = 0; // 子Fiber中的索引位置this.ref = null; // 如果組件上有ref屬性,則該屬性指向它this.refCleanup = null; // 如果組件上的ref屬性在更新中被刪除或更改,此字段會用于追蹤需要清理的舊ref// Props & Statethis.pendingProps = pendingProps; // 正在等待處理的新propsthis.memoizedProps = null; // 上一次渲染時的propsthis.updateQueue = null; // 一個隊列,包含了該Fiber上的狀態更新和副作用this.memoizedState = null; // 上一次渲染時的statethis.dependencies = null; // 該Fiber訂閱的上下文或其他資源的描述// 工作模式this.mode = mode; // 描述Fiber工作模式的標志(例如Concurrent模式、Blocking模式等)。// Effectsthis.flags = NoFlags; // 描述該Fiber發生的副作用的標志(十六進制的標識)this.subtreeFlags = NoFlags; // 描述該Fiber子樹中發生的副作用的標志(十六進制的標識)this.deletions = null; // 在commit階段要刪除的子Fiber數組this.lanes = NoLanes; // 與React的并發模式有關的調度概念。this.childLanes = NoLanes; // 與React的并發模式有關的調度概念。this.alternate = null; // Current Tree和Work-in-progress (WIP) Tree的互相指向對方tree里的對應單元// 如果啟用了性能分析if (enableProfilerTimer) {// ……}// 開發模式中if (__DEV__) {// ……}
}

工作單元與任務調度:每個Fiber節點代表一個工作單元,React通過任務調度器根據任務的優先級來決定執行順序,實現可中斷和恢復的任務調度。

任務調度的實現:
調度主要涉及兩個核心概念,優先級控制權

  1. 每個任務在調和的過程中,有自己的優先級,react之前的版本用expirationTime屬性代表優先級,該優先級和IO不能很好的搭配工作(io的優先級高于cpu的優先級),現在有了更加細粒度的優先級表示方法Lane,Lane用二進制位表示優先級,二進制中的1表示位置,同一個二進制數可以有多個相同優先級的位,這就可以表示‘批’的概念,而且二進制方便計算。
  2. 在構建和更新用戶界面時,React Fiber 會分批處理任務,并在每批任務之間讓出控制權,以便瀏覽器可以執行其他操作。在 React
    中,讓出控制權的技術通常是通過使用瀏覽器的 requestIdleCallback 函數實現的,但是 react
    考慮到兼容性問題并沒有使用該 API,而是自己實現了一套機制,通過MessageChannel +
    requestAnimationFrame
    自己模擬實現了requestIdleCallback。
  3. 具體過程:Scheduer 中通過 performance.now() 來獲取 當前時間,在產生的一個任務對象中會記錄該任務的開始時間(這個開始時間會根據任務優先級的不同添加不同的delay)和過期時間(這個過期時間是開始時間+timeout,不同的優先級有不同的timeout,過期時間小于開始時間時說明是立即執行),整個任務流水線在開始執行時也會記錄一個全局的開始時間(每次任務中斷后,在再次執行時,相當于開啟了一個新的任務流水線,所以這個全局的開始時間會進行更新),當任務到了過期時間,并且沒有到達需要瀏覽器渲染的時候就執行任務。

為什么不是setTimeout?
因為setTimeout的遞歸層級過深的話,延遲就不是1ms,而是4ms,這樣會造成延遲時間過長
為什么不是requestAnimationFrame?
requestAnimationFrame是在微任務執行完之后,瀏覽器重排重繪之前執行,執行的時機是不準確的。如果raf之前JS的執行時間過長,依然會造成延遲。
與setTimeout相比,requestAnimationFrame最大的優勢是由系統來決定回調函數的執行時機。(如果屏幕刷新率是60Hz,那么回調函數就每16.7ms被執行一次)
為什么不是requestIdleCallback?
requestIdleCallback的執行時機是在瀏覽器重排重繪之后,也就是瀏覽器的空閑時間執行。其實執行的時機依然是不準確的。
為什么是 MessageChannel?
首先,MessageChannel的執行時機比setTimeout靠前。其次,requestIdleCallback并不是所有瀏覽器都支持的。為了解決這個問題,React采用MessageChannel來模擬requestIdleCallback。
如何
如何判斷有沒有到達需要瀏覽器渲染的時候?
通過 shouldYieldToHost 方法來判斷是否應該暫停任務流水線,歸還主線程來進行渲染操作,shouldYieldToHost 中會獲取當前時間,并減去全局的開始時間,如果這個差值大于了 Scheduer 設置的臨界值(5ms),說明任務流水線執行時間有點長了,需要歸還主線程,于是 shouldYieldToHost 返回 true。

工作原理

1.單元工作:每個Fiber節點代表一個單元,所有Fiber節點共同組成一個Fiber鏈表樹(有鏈接屬性,同時又有樹的結構),這種結構讓React可以細粒度控制節點的行為。
2.鏈接屬性:child、sibling和return字段構成了Fiber之間的鏈接關系,使React能夠遍歷組件樹并知道從哪里開始、繼續或停止工作。
child指向子節點,sibing指向兄弟節點,return指向兄弟節點。
fiber結構
注:遍歷順序為ABCED

為什么選擇鏈表?
Fiber 采用鏈表數據結構的原因是因為鏈表可以方便地在列表的中間插入和刪除元素。這在構建和更新用戶界面時非常有用,因為可能會有大量的元素需要插入或刪除。

3.雙緩沖技術:React在更新時,會根據現有的Fiber樹(Current Tree)創建一個新的臨時樹(Work-in-progress (WIP) Tree),WIP-Tree包含了當前更新受影響的最高節點直至其所有子孫節點。Current Tree是當前顯示在頁面上的視圖,WIP-Tree則是在后臺進行更新,WIP-Tree更新完成后會復制其它節點,并最終替換掉Current Tree,成為新的Current Tree。因為React在更新時總是維護了兩個Fiber樹,所以可以隨時進行比較、中斷或恢復等操作,而且這種機制讓React能夠同時具備優秀的渲染性能和UI的穩定性。

Reconciler中在進行虛擬DOM的diff,它之所以可以中斷,是因為fiber鏈表的這種結構,我們只要保存當前任務的指針就可以在下次直接找到該任務并執行,所以這種架構天然支持中斷,并且 Fiber 雙緩存 構建的兩顆樹中的 wip Fiber Tree 中保存著已經構建的虛擬DOM,當中斷繼續運行時根據保存的指針找到未完成的任務繼續進行協調構建 wip Fiber Tree。

雙緩存

4.State和Props
props是組件的屬性,是父組件傳遞給子組件的數據。在Fiber中,props被存儲在Fiber節點的memoizedProps和pendingProps字段中。

  • memoizedProps:表示上一次渲染時的props。這是在組件完成渲染后保存的props的副本。
  • pendingProps:表示正在等待處理的新props。當組件接收到新的props時,這些props會被存儲在pendingProps中,等待下一次渲染。

React通過比較memoizedProps和pendingProps來確定組件的props是否發生了變化。如果發生變化,React會觸發組件的更新。

state是組件的內部狀態,通常由組件自身管理。在Fiber中,state被存儲在Fiber節點的memoizedState字段中。

  • memoizedState:表示上一次渲染時的state。這是在組件完成渲染后保存的state的副本。

5.副作用的追蹤:副作用的追蹤是一個關鍵機制,它允許React在渲染過程中收集和管理需要執行的副作用操作。這些副作用操作包括DOM更新、生命周期方法調用等。

  1. 標記副作用:

    每個Fiber節點都有一個flags字段和一個subtreeFlags字段,用于標識該節點及其子樹中需要執行的副作用。flags字段直接標記該Fiber節點的副作用類型,而subtreeFlags字段則用于標記其子樹中所有Fiber節點的副作用類型。這些副作用類型包括但不限于:

Placement:表示需要將一個DOM節點插入到DOM樹中。
Update:表示需要更新一個DOM節點。
Deletion:表示需要從DOM樹中刪除一個DOM節點。

React 的所有 effect 類型都在這里 packages/shared/ReactSideEffectTags.js。

2.構建副作用鏈表

在React的渲染流程中,render階段會從根節點開始處理所有的Fiber節點,收集有副作用的Fiber節點,并構建副作用鏈表。這個鏈表是通過Fiber節點的nextEffect指針連接而成的。在completeUnitOfWork階段,每個Fiber節點會將自己的副作用鏈表提交給其父節點。具體步驟如下:

  • 提交副作用鏈表:如果父節點沒有副作用鏈表,則將父節點的firstEffect指向當前節點的firstEffect。如果當前節點有副作用鏈表,則將父節點的lastEffect的nextEffect指向當前節點的firstEffect。
  • 添加當前節點到父節點的副作用鏈表:如果當前節點本身有副作用(即flags > 1),則將當前節點添加到父節點的副作用鏈表中。

3.執行副作用
在commit階段,React會遍歷副作用鏈表,并根據每個Fiber節點的flags標志執行對應的副作用操作。這個階段是不可中斷的,以確保所有副作用要么全部提交,要么全部不提交。具體操作包括:

  • Placement:調用commitPlacement執行實際的插入操作,并清除Placement標記。
  • Update:調用commitWork執行實際的更新操作,并清除Update標記。
  • Deletion:調用commitDeletion執行實際的刪除操作,并清除Deletion標記。

4.使用位操作
React使用位操作來處理副作用標記,因為位操作可以高效地進行集合操作。例如,通過按位或操作(|),可以將多個副作用標記合并到一個字段中;通過按位與操作(&),可以檢查一個Fiber節點是否具有特定的副作用標記。

注:這也是Fiber架構更快的原因之一

工作流程

React Fiber的工作流程主要分為兩個階段:Reconciliation(調和)Commit(提交)

調和
調和的主要目標是在構建工作樹的階段,通過比較新的props和舊的Fiber樹來確定哪些部分需要更新。
分三個階段:

  1. beginWork:創建和標記更新節點

    1.1判斷節點是否要更新

// packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime,
): Fiber | null {switch (workInProgress.tag) {case ClassComponent: {return updateClassComponent(current, workInProgress, Component, resolvedProps);}}
}function updateClassComponent(current: Fiber | null, workInProgress: Fiber, Component: any, nextProps) {const nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate);return nextUnitOfWork;
}
function finishClassComponent(current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean
) {return workInProgress.child; 
}

beginWork 本身對遞歸沒什么實際進展,主要是根據 tag 分發邏輯。

1.2 判斷節點更新還是復用

function updateClassComponent(current, workInProgress, Component, nextProps) {// 確定組件是否應該更新const shouldUpdate = determineIfComponentShouldUpdate(current,workInProgress,nextProps);// 傳遞正確的 shouldUpdate 參數const nextUnitOfWork = finishClassComponent(current,workInProgress,Component,shouldUpdate,false // 假設沒有 context);return nextUnitOfWork;
}function finishClassComponent(current,workInProgress,Component,shouldUpdate,hasContext
) {// 如果不需要更新,則復用當前子樹if (!shouldUpdate) {cloneChildFibers(current, workInProgress);return workInProgress.child;}// 繼續正常的更新流程return workInProgress.child;
}function determineIfComponentShouldUpdate(current, workInProgress, nextProps) {// 首次渲染時總是更新if (current === null) {return true;}// 淺比較 props 判斷是否需要更新const prevProps = current.memoizedProps;// 檢查 props 數量是否相同if (Object.keys(prevProps).length !== Object.keys(nextProps).length) {return true;}// 檢查每個 prop 是否相同for (let key in prevProps) {if (prevProps[key] !== nextProps[key]) {// 特殊處理 childrenif (key === 'children') {// 簡化的 children 比較,實際可能需要更復雜的比較if (prevProps.children !== nextProps.children) {return true;}} else {return true;}}}return false;
}// 輔助函數:復用當前子樹結構
function cloneChildFibers(current, workInProgress) {// 簡化實現,實際 React 中會更復雜if (current.child) {workInProgress.child = {...current.child,alternate: current.child,return: workInProgress};// 遞歸克隆所有子節點cloneChildren(current.child, workInProgress.child);}
}function cloneChildren(currentParent, wipParent) {let currentChild = currentParent.child;let wipChild = null;let prevWipChild = null;while (currentChild) {const newChild = {...currentChild,alternate: currentChild,return: wipParent,sibling: null};if (!wipChild) {wipParent.child = newChild;wipChild = newChild;} else {prevWipChild.sibling = newChild;}prevWipChild = newChild;currentChild = currentChild.sibling;}
}

2.completeUnitOfWork和completeWork:收集副作用列表
2.1completeUnitOfWork 負責遍歷Fiber節點,同時記錄了有副作用節點的關系

// packages/react-reconciler/src/ReactFiberWorkLoop.js
function completeUnitOfWork(unitOfWork: Fiber): void {let completedWork: Fiber = unitOfWork; // 當前正在完成的工作單元do {const current = completedWork.alternate; // 當前Fiber節點在另一棵樹上的版本const returnFiber = completedWork.return; // 當前Fiber節點的父節點let next;next = completeWork(current, completedWork, renderLanes); // 調用completeWork函數if (next !== null) {// 當前Fiber還有工作要完成workInProgress = next;return;}const siblingFiber = completedWork.sibling;if (siblingFiber !== null) {// 如果有兄弟節點,則進入兄弟節點的工作workInProgress = siblingFiber;return;}// 如果沒有兄弟節點,回到父節點繼續completedWork = returnFiber;workInProgress = completedWork;} while (completedWork !== null);// 如果處理了整個Fiber樹,更新workInProgressRootExitStatus為RootCompleted,表示調和已完成if (workInProgressRootExitStatus === RootInProgress) {workInProgressRootExitStatus = RootCompleted;} 
}

需要注意的是,next 指針不應該重復經過同一個節點。因為如果向下的過程中經過某個節點,在向上的過程中又出現,就會再次進入 beginWork,造成死循環。
completeUnitOfWork 內部又創建了一層循環,搭配一個向上的新指針 workInProgress,然后循環看當前指針節點,有兄弟節點就返回交還給外層循環,沒有就向上到父節點,直到最上面的根節點。

2.2 completeWork 在 completeUnitOfWork 中被調用,主要是根據 tag 進行不同的處理。

// packages/react-reconciler/src/ReactFiberCompleteWork.js
function completeWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {const newProps = workInProgress.pendingProps;switch (workInProgress.tag) {// 多種tagcase FunctionComponent:case ForwardRef:case SimpleMemoComponent:bubbleProperties(workInProgress)return null;case ClassComponent:// 省略邏輯// ……bubbleProperties(workInProgress)return null;case HostComponent:// 省略邏輯// ……return null;// 多種tag// ……}
}

從源碼中可以看出來,completeWork起到一個分支處理的作用,在分支里,調用了bubbleProperties函數,這個函數主要用于記錄Fiber的副作用標志為子Fiber創建鏈表

// packages/react-reconciler/src/ReactFiberCompleteWork.jsfunction bubbleProperties(completedWork: Fiber) {const didBailout =completedWork.alternate !== null &&completedWork.alternate.child === completedWork.child; // 當前的Fiber與其alternate(備用/上一次的Fiber)有相同的子節點,則跳過更新let newChildLanes = NoLanes; // 合并后的子Fiber的laneslet subtreeFlags = NoFlags; // 子樹的flags。if (!didBailout) {// 沒有bailout,需要冒泡子Fiber的屬性到父Fiberlet child = completedWork.child;// 遍歷子Fiber,并合并它們的lanes和flagswhile (child !== null) {newChildLanes = mergeLanes(newChildLanes,mergeLanes(child.lanes, child.childLanes),);subtreeFlags |= child.subtreeFlags;subtreeFlags |= child.flags;child.return = completedWork; // Fiber的return指向父Fiber,確保整個Fiber樹的一致性child = child.sibling;}completedWork.subtreeFlags |= subtreeFlags; // 合并所有flags(副作用)} else {// 有bailout,只冒泡那些具有“靜態”生命周期的flagslet child = completedWork.child;while (child !== null) {newChildLanes = mergeLanes(newChildLanes,mergeLanes(child.lanes, child.childLanes),);subtreeFlags |= child.subtreeFlags & StaticMask; // 不同subtreeFlags |= child.flags & StaticMask; // 不同child.return = completedWork;child = child.sibling;}completedWork.subtreeFlags |= subtreeFlags;}completedWork.childLanes = newChildLanes; // 獲取所有子Fiber的lanes。return didBailout;
}

調和過程可以被中斷,那么在源碼中是怎么實現的呢?

整體流程:
1.上下文準備:保存當前執行上下文和 dispatcher,設置渲染上下文
2.工作進度檢查:判斷是否需要為新渲染任務準備新的工作進度樹
3.并發工作循環:持續處理工作單元,直到完成或被掛起
4.掛起處理:根據不同掛起原因(數據、錯誤、實例等)執行不同恢復策略
5.上下文恢復:重置執行上下文和 dispatcher
6.狀態返回:根據渲染完成情況返回相應狀態

// packages/react-reconciler/src/ReactFiberWorkLoop.js
// 以下只是核心邏輯的代碼,不是renderRootConcurrent的完整源碼
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {// 保存當前的執行上下文和 dispatcherconst prevExecutionContext = executionContext;executionContext |= RenderContext;const prevDispatcher = pushDispatcher(root.containerInfo);const prevCacheDispatcher = pushCacheDispatcher();if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {// 如果當前的工作進度樹與傳入的 root 或 lanes 不匹配,我們需要為新的渲染任務準備一個新的堆棧。// ……}// 持續的工作循環,除非中斷發生,否則會一直嘗試完成渲染工作outer: do {try {if (workInProgressSuspendedReason !== NotSuspended &&workInProgress !== null) {// 如果當前的工作進度是由于某種原因而被掛起的,并且仍然有工作待處理,那么會處理它const unitOfWork = workInProgress;const thrownValue = workInProgressThrownValue;// 根據不同掛起原因,進行中斷、恢復等計算resumeOrUnwind: switch (workInProgressSuspendedReason) {case SuspendedOnError: {// 如果工作因錯誤被掛起,那么工作會被中斷,并從最后一個已知的穩定點繼續// ……省略邏輯break;}case SuspendedOnData: {// 工作因等待數據(通常是一個異步請求的結果)而被掛起,// ……省略邏輯break outer;}case SuspendedOnInstance: {// 將掛起的原因更新為SuspendedOnInstanceAndReadyToContinue并中斷工作循環,標記為稍后準備好繼續執行workInProgressSuspendedReason = SuspendedOnInstanceAndReadyToContinue;break outer;}case SuspendedAndReadyToContinue: {// 表示之前的掛起工作現在已經準備好繼續執行if (isThenableResolved(thenable)) {// 如果已解析,這意味著需要的數據現在已經可用workInProgressSuspendedReason = NotSuspended;workInProgressThrownValue = null;replaySuspendedUnitOfWork(unitOfWork); // 恢復執行被掛起的工作} else {workInProgressSuspendedReason = NotSuspended;workInProgressThrownValue = null;throwAndUnwindWorkLoop(unitOfWork, thrownValue); // 繼續循環}break;}case SuspendedOnInstanceAndReadyToContinue: {// ……省略部分邏輯const isReady = preloadInstance(type, props);if (isReady) {// 實例已經準備好workInProgressSuspendedReason = NotSuspended; // 該fiber已完成,不需要再掛起workInProgressThrownValue = null;const sibling = hostFiber.sibling;if (sibling !== null) {workInProgress = sibling; // 有兄弟節點,開始處理兄弟節點} else {// 沒有兄弟節點,回到父節點const returnFiber = hostFiber.return;if (returnFiber !== null) {workInProgress = returnFiber;completeUnitOfWork(returnFiber); // 收集副作用,前面有詳細介紹} else {workInProgress = null;}}break resumeOrUnwind;}}// 還有其它case}}workLoopConcurrent(); // 如果沒有任何工作被掛起,那么就會繼續處理工作循環。break;} catch (thrownValue) {handleThrow(root, thrownValue);}} while (true);// 重置了之前保存的執行上下文和dispatcher,確保后續的代碼不會受到這個函數的影響resetContextDependencies();popDispatcher(prevDispatcher);popCacheDispatcher(prevCacheDispatcher);executionContext = prevExecutionContext;// 檢查調和是否已完成if (workInProgress !== null) {// 未完成return RootInProgress; // 返回一個狀態值,表示還有未完成} else {// 已完成workInProgressRoot = null; // 重置rootworkInProgressRootRenderLanes = NoLanes; // 重置LanefinishQueueingConcurrentUpdates(); // 處理隊列中的并發更新return workInProgressRootExitStatus; // 返回當前渲染root的最終退出狀態}
}

在這里插入圖片描述

提交

提交是遍歷在Reconciliation階段創建的副作用列表進行更新DOM并執行任何副作用。

主要分三個階段:

  1. BeforeMutation:遍歷副作用列表
// packages/react-reconciler/src/ReactFiberCommitWork.js
export function commitBeforeMutationEffects(root: FiberRoot,firstChild: Fiber,
): boolean {nextEffect = firstChild; // nextEffect是遍歷此鏈表時的當前fibercommitBeforeMutationEffects_begin(); // 遍歷fiber,處理節點刪除和確認節點在before mutation階段是否有要處理的副作用const shouldFire = shouldFireAfterActiveInstanceBlur; // 當一個焦點元素被刪除或隱藏時,它會被設置為 trueshouldFireAfterActiveInstanceBlur = false;focusedInstanceHandle = null;return shouldFire;
}

這個沒什么可說的,就是對副作用進行遍歷,調用 getSnapshotBeforeUpdate 等生命周期方法。

  1. CommitMutation:正式提交
// packages/react-reconciler/src/ReactFiberCommitWork.jsexport function commitMutationEffects(root: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,
) {// lanes和root被設置為"in progress"狀態,表示它們正在被處理inProgressLanes = committedLanes;inProgressRoot = root;// 遞歸遍歷Fiber,更新副作用節點commitMutationEffectsOnFiber(finishedWork, root, committedLanes);// 重置進行中的lanes和rootinProgressLanes = null;inProgressRoot = null;
}

commitMutationEffects 函數是 React 提交階段的入口點,主要執行以下操作:

  • 狀態準備:設置全局變量標記當前正在處理的 root 和 lanes
  • 副作用執行:遞歸遍歷 Fiber 樹,執行所有需要提交的副作用(DOM 更新、生命周期調用等)
  • 狀態重置:清除全局標記,完成提交階段

3.commitLayout:處理layout effects

// packages/react-reconciler/src/ReactFiberCommitWork.js
export function commitLayoutEffects(finishedWork: Fiber,root: FiberRoot,committedLanes: Lanes,
): void {inProgressLanes = committedLanes;inProgressRoot = root;// 創建一個current指向就Fiber樹的alternateconst current = finishedWork.alternate;// 處理那些由useLayoutEffect創建的layout effectscommitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);inProgressLanes = null;inProgressRoot = null;
}

提交階段三個子階段的核心區別

階段執行時機核心操作示例操作
Before MutationDOM 更新讀取 DOM 狀態(如 scroll 位置)getSnapshotBeforeUpdate、調度 useEffect
MutationDOM 更新執行 DOM 插入 / 更新 / 刪除操作commitPlacementcommitUpdatecommitDeletion
LayoutDOM 更新,渲染同步執行依賴 DOM 的副作用、調用生命周期方法componentDidMountcomponentDidUpdateuseLayoutEffect 的回調函數

提交之后就無法中斷了。

優勢特點

提升性能:通過可中斷的渲染和增量渲染,避免了長時間占用主線程資源,減少了卡頓現象,提高了應用的性能。
優化用戶體驗:能夠根據任務優先級進行調度,確保高優先級任務得到及時處理,提升了應用的交互體驗。
支持新特性:為React引入一些新特性提供了基礎,如異步渲染、懶加載等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/86628.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86628.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86628.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Modbus 數據模型:線圈、寄存器與功能碼詳解(二)

三、Modbus 功能碼詳解 3.1 功能碼分類與作用 Modbus 功能碼是 Modbus 通信協議中的關鍵組成部分&#xff0c;它如同一個 “指令指揮官”&#xff0c;在通信事務處理中扮演著核心角色。功能碼占用 1 個字節的空間&#xff0c;取值范圍為 1 到 255 &#xff08;0x01 - 0xFF&am…

多表連接查詢:語法、注意事項與最佳實踐

&#x1f517; 多表連接查詢&#xff1a;語法、注意事項與最佳實踐 多表連接是 SQL 的核心能力&#xff0c;用于關聯多個表的數據。以下是深度解析&#xff0c;涵蓋語法規范、性能陷阱及實戰技巧&#xff1a; &#x1f4dc; 一、多表連接語法大全 1. 顯式連接&#xff08;推薦…

使用Calibre對GDS進行數據遍歷

在芯片的GDS數據里&#xff0c;使用Calibre對數據進行處理是非常常見的操作&#xff0c;但是GDS是一種和常規設計結構不太一樣的一種數據&#xff0c;這里&#xff0c;通過這個小小的科普文章&#xff0c;一起看看怎么樣在GDS里邊做數據漫游吧&#xff01;閑言少敘&#xff0c;…

PyQtNode Editor 第二篇自定義可視化視圖

在第一篇博客中,我們已經完成了 PyQtNode Editor 的基礎環境搭建,并深入解析了自定義圖形場景QDMGraphicsScene的實現原理。那個帶有網格背景的場景就像一張空白的圖紙,現在我們要在這張圖紙上開始繪制真正的節點系統。 今天我們將聚焦于節點編輯器的核心數據結構設計,實現…

【擴歐應用】同余方程

與擴歐的聯系 在同余方程的求解過程中&#xff0c;我們通常需要將方程轉化為線性不定方程&#xff08;Diophantine 方程&#xff09;的形式&#xff0c;然后使用擴展歐幾里得算法&#xff08;Extended Euclidean Algorithm, EEA&#xff09;求解。 同余方程是怎么轉化為線性不…

結構化數據:NumPy 的結構化數組

文章目錄 結構化數據&#xff1a;NumPy 的結構化數組探索結構化數組的創建更高級的復合類型記錄數組&#xff1a;結構化數組的變體走向 Pandas 結構化數據&#xff1a;NumPy 的結構化數組 雖然我們的數據通常可以用同質數組很好地表示&#xff0c;但有時情況并非如此。本文將演…

phpcms 更換新域名更新欄目url和內容頁url無法更新解決方法

更換域名后更新欄目url和內容頁url還是無法更新為新的域名&#xff0c;手動把cache文件夾下能清除的緩存文件清除了還是不行&#xff0c;把數據庫的緩存表內容清空了還是不行&#xff0c;問題在于欄目緩存并沒有清除。 解決辦法: (1)、找到文件&#xff1a;/caches/configs/sys…

瑪哈特七輥矯平機:板材平整的精密衛士

在金屬板材加工領域&#xff0c;表面平整度是衡量產品質量的核心指標之一。無論是汽車覆蓋件、精密儀器外殼&#xff0c;還是建筑裝飾板材&#xff0c;任何彎曲、波浪或翹曲都將嚴重影響后續加工精度、產品強度及美觀度。七輥矯平機&#xff0c;憑借其獨特的輥系結構設計&#…

融合聚類與分類的退役鋰電智能分選技術:助力新能源汽車產業可持續發展

融合聚類與分類的退役鋰電智能分選技術&#xff1a;助力新能源汽車產業可持續發展 關鍵詞&#xff1a;退役鋰離子電池分選 | 聚類分類融合 | 電化學阻抗譜(EIS) | 動態時間規整(DTW) | 多模態分類模型 新能源汽車 | 電池梯次利用 | 增量學習 | 數字孿生 | 聯邦學習 | 雙流特征…

jenkins中執行python腳本導入路徑錯誤

&#x1f9fe; 問題一&#xff1a;ModuleNotFoundError: No module named jenkins &#x1f50d; 現象&#xff1a; 在本地運行正常&#xff0c;但在 Jenkins 中運行腳本時報錯&#xff0c;提示找不到 jenkins 模塊。 ? 原因分析&#xff1a; Python 默認只從當前目錄或已…

華為云Flexus+DeepSeek征文 | 華為云ModelArts Studio實戰指南:創建高效的AingDesk知識庫問答助手

華為云FlexusDeepSeek征文 | 華為云ModelArts Studio實戰指南&#xff1a;創建高效的AingDesk知識庫問答助手 前言一、ModelArts Studio介紹1. 華為云ModelArts Studio簡介2. 華為云ModelArts Studio主要特點3. 華為云ModelArts Studio主要使用場景 二、AingDesk介紹1. AingDes…

NLP基礎1_word-embedding

基于github項目&#xff1a;https://github.com/shibing624/nlp-tutorial/tree/main 自然語言處理任務 1) 簡單任務 拼寫檢查 Spell Checking 關鍵詞檢索 Keyword Search 同義詞查找 Finding Synonyms 2) 中級任務 解析來自網站、文檔等的信息 3) 復雜任務 機器翻譯 Ma…

ClickHouse系列--BalancedClickhouseDataSource實現

clickhouse-jdbc中負載均衡數據源的實現。 基本邏輯如下&#xff1a; 1.通過配置的url串&#xff0c;來切分構造url列表&#xff1b; 2.通過一個定時線程任務&#xff0c;來不斷的去ping url列表&#xff0c;來更新可用的url列表&#xff1b; 3.在可用列表中隨機返回一個可用ur…

Linux目錄說明

Linux Filesystem Hierarchy Standard&#xff08;FHS&#xff09; 1. /bin 全稱&#xff1a;Binary&#xff08;二進制文件&#xff09;功能&#xff1a;存放系統最基礎的可執行命令&#xff0c;所有用戶&#xff08;包括普通用戶&#xff09;都能使用&#xff0c;用于系統啟…

鴻蒙 Grid 與 GridItem 深度解析:二維網格布局解決方案

一、引言&#xff1a;網格布局 —— 多維度數據展示的黃金方案 在鴻蒙應用開發體系中&#xff0c;網格布局作為處理多元素有序排列的核心方案&#xff0c;廣泛應用于電商商品陳列、圖片畫廊、功能矩陣等場景。鴻蒙提供的 Grid 與 GridItem 組件通過聲明式語法構建靈活的二維布…

??Vue 開發環境配置:使用 devServer.proxy 解決跨域問題?-vue中文件vue.config,js中配置devserver做反向代理到后端

??Vue 開發環境配置&#xff1a;使用 devServer.proxy 解決跨域問題?? ??引言?? 在現代 Web 開發中&#xff0c;前端和后端通常獨立開發&#xff0c;前端運行在 http://localhost:8080&#xff0c;而后端可能運行在 http://localhost:8000 或其他端口。由于瀏覽器的 …

JVM 中的 GC 算法演進之路!(Serial、CMS、G1 到 ZGC)

引言 想象一下&#xff0c;Java 程序運行就像在一個巨大的圖書館里借書還書。這個圖書館&#xff08;JVM 的內存堆區&#xff09;為了高效運轉&#xff0c;需要一個聰明的“圖書管理員”來清理失效的書籍&#xff08;垃圾對象&#xff09;。這&#xff0c;就是垃圾回收器&#…

(9)python+playwright自動化測試-頁面(page)

1.簡介 通過前邊的講解和學習&#xff0c;細心認真地你可能發現在Playwright中&#xff0c;沒有Element這個概念&#xff0c;只有Page的概念&#xff0c;Page不僅僅指的是某個頁面&#xff0c;例如頁面間的跳轉等&#xff0c;還包含了所有元素、事件的概念&#xff0c;所以我們…

《自動控制原理 》- 第 1 章 自動控制的基本原理與方式

1-1 自動控制的基本原理與方式 自動控制是指在沒有人直接參與的情況下&#xff0c;利用外加的設備或裝置&#xff0c;使機器、設備或生產過程的某個工作狀態或參數按照預定的規律運行。自動控制的核心原理是反饋控制&#xff0c;即通過將系統的輸出量回送到輸入端&#xff0c;與…

DL00715-基于YOLOv11的水面漂浮物目標檢測含數據集

【論文必備】基于YOLOv11的水面漂浮物目標檢測——讓你的研究走在科技前沿&#xff01; 在環境監測、海洋保護和水質管理領域&#xff0c;水面漂浮物的檢測一直是一個亟待解決的難題。傳統的人工巡檢方式不僅耗時費力&#xff0c;還無法覆蓋廣泛的水域范圍。如今&#xff0c;基…