文章目錄
- 產生背景
- 核心概念
- 工作原理
- 工作流程
- 優勢特點
產生背景
在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通過任務調度器根據任務的優先級來決定執行順序,實現可中斷和恢復的任務調度。
任務調度的實現:
調度主要涉及兩個核心概念,優先級和控制權。
- 每個任務在調和的過程中,有自己的優先級,react之前的版本用expirationTime屬性代表優先級,該優先級和IO不能很好的搭配工作(io的優先級高于cpu的優先級),現在有了更加細粒度的優先級表示方法Lane,Lane用二進制位表示優先級,二進制中的1表示位置,同一個二進制數可以有多個相同優先級的位,這就可以表示‘批’的概念,而且二進制方便計算。
- 在構建和更新用戶界面時,React Fiber 會分批處理任務,并在每批任務之間讓出控制權,以便瀏覽器可以執行其他操作。在 React
中,讓出控制權的技術通常是通過使用瀏覽器的 requestIdleCallback 函數實現的,但是 react
考慮到兼容性問題并沒有使用該 API,而是自己實現了一套機制,通過MessageChannel +
requestAnimationFrame自己模擬實現了requestIdleCallback。 - 具體過程: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指向兄弟節點。
注:遍歷順序為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更新、生命周期方法調用等。
-
標記副作用:
每個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樹來確定哪些部分需要更新。
分三個階段:
-
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并執行任何副作用。
主要分三個階段:
- 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 等生命周期方法。
- 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 Mutation | DOM 更新前 | 讀取 DOM 狀態(如 scroll 位置) | getSnapshotBeforeUpdate 、調度 useEffect |
Mutation | DOM 更新中 | 執行 DOM 插入 / 更新 / 刪除操作 | commitPlacement 、commitUpdate 、commitDeletion |
Layout | DOM 更新后,渲染前 | 同步執行依賴 DOM 的副作用、調用生命周期方法 | componentDidMount 、componentDidUpdate 、useLayoutEffect 的回調函數 |
提交之后就無法中斷了。
優勢特點
提升性能:通過可中斷的渲染和增量渲染,避免了長時間占用主線程資源,減少了卡頓現象,提高了應用的性能。
優化用戶體驗:能夠根據任務優先級進行調度,確保高優先級任務得到及時處理,提升了應用的交互體驗。
支持新特性:為React引入一些新特性提供了基礎,如異步渲染、懶加載等。