您好,登錄后才能下訂單哦!
這篇文章主要講解了“React Fiber樹是怎么構建與更新的”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“React Fiber樹是怎么構建與更新的”吧!
Lin Clark 在 React Conf 2017 的演講中,他通過漫畫的形式,很好地講述了 fiber 為何出現,下面我根據她的演講,結合我自己的理解來談一談 fiber 出現的原因。
在 react15 及之前 fiber 未出現時,react 的一系列執行過程例如生命周期執行、虛擬 dom 的比較、dom 樹的更新等都是同步的,一旦開始執行就不會中斷,直到所有的工作流程全部結束為止。
要知道,react 所有的狀態更新,都是從根組件開始的,當應用組件樹比較龐大時,一旦狀態開始變更,組件樹層層遞歸開始更新,js 主線程就不得不停止其他工作。例如組件樹一共有 1000 個組件需要更新,每個組件更新所需要的時間為 1s,那么在這 1s 內瀏覽器都無法做其他的事情,用戶的點擊輸入等交互事件、頁面動畫等都不會得到響應,體驗就會非常的差。
這種情況下,函數堆棧的調用就像下圖一樣,層級很深,很長時間不會返回
為了解決這一問題,react 引入了 fiber 這種數據結構,將更新渲染耗時長的大任務,分為許多的小片。每個小片的任務執行完成后,都先去執行其他高優先級的任務(例如用戶點擊輸入事件、動畫等),這樣 js 的主線程就不會被 react 獨占,雖然任務執行的總時間不變,但是頁面能夠及時響應高優先級任務,顯得不會卡頓了。
fiber 分片模式下,瀏覽器主線程能夠定期被釋放,保證了渲染的幀率,函數的堆棧調用如下(波谷表示執行分片任務,波峰表示執行其他高優先級任務):
react 通過 fiber,為我們提供了一種跟蹤、調度、暫停和中止工作的便捷方式,保證了頁面的性能和流暢度。
fiber 是一種數據結構,每個 fiber 節點的內部,都保存了 dom 相關信息、fiber 樹相關的引用、要更新時的副作用等,我們可以看一下源碼中的 fiber 結構:
// packages/react-reconciler/src/ReactInternalTypes.jsexport type Fiber = {| // 作為靜態數據結構,存儲節點 dom 相關信息 tag: WorkTag, // 組件的類型,取決于 react 的元素類型 key: null | string, elementType: any, // 元素類型 type: any, // 定義與此fiber關聯的功能或類。對于組件,它指向構造函數;對于DOM元素,它指定HTML tag stateNode: any, // 真實 dom 節點 // fiber 鏈表樹相關 return: Fiber | null, // 父 fiber child: Fiber | null, // 第一個子 fiber sibling: Fiber | null, // 下一個兄弟 fiber index: number, // 在父 fiber 下面的子 fiber 中的下標 ref: | null | (((handle: mixed) => void) & {_stringRef: ?string, ...}) | RefObject, // 工作單元,用于計算 state 和 props 渲染 pendingProps: any, // 本次渲染需要使用的 props memoizedProps: any, // 上次渲染使用的 props updateQueue: mixed, // 用于狀態更新、回調函數、DOM更新的隊列 memoizedState: any, // 上次渲染后的 state 狀態 dependencies: Dependencies | null, // contexts、events 等依賴 mode: TypeOfMode, // 副作用相關 flags: Flags, // 記錄更新時當前 fiber 的副作用(刪除、更新、替換等)狀態 subtreeFlags: Flags, // 當前子樹的副作用狀態 deletions: Array<Fiber> | null, // 要刪除的子 fiber nextEffect: Fiber | null, // 下一個有副作用的 fiber firstEffect: Fiber | null, // 指向第一個有副作用的 fiber lastEffect: Fiber | null, // 指向最后一個有副作用的 fiber // 優先級相關 lanes: Lanes, childLanes: Lanes, alternate: Fiber | null, // 指向 workInProgress fiber 樹中對應的節點 actualDuration?: number, actualStartTime?: number, selfBaseDuration?: number, treeBaseDuration?: number, _debugID?: number, _debugSource?: Source | null, _debugOwner?: Fiber | null, _debugIsCurrentlyTiming?: boolean, _debugNeedsRemount?: boolean, _debugHookTypes?: Array<HookType> | null,|};
fiber 中和 dom 節點相關的信息主要關注 tag
、key
、type
和 stateNode
。
fiber 中 tag
屬性的 ts 類型為 workType,用于標記不同的 react 組件類型,我們可以看一下源碼中 workType 的枚舉值:
// packages/react-reconciler/src/ReactWorkTags.jsexport const FunctionComponent = 0;export const ClassComponent = 1;export const IndeterminateComponent = 2; // Before we know whether it is function or classexport const HostRoot = 3; // Root of a host tree. Could be nested inside another node.export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.export const HostComponent = 5;export const HostText = 6;export const Fragment = 7;export const Mode = 8;export const ContextConsumer = 9;export const ContextProvider = 10;export const ForwardRef = 11;export const Profiler = 12;export const SuspenseComponent = 13;export const MemoComponent = 14;export const SimpleMemoComponent = 15;export const LazyComponent = 16;export const IncompleteClassComponent = 17;export const DehydratedFragment = 18;export const SuspenseListComponent = 19;export const FundamentalComponent = 20;export const ScopeComponent = 21;export const Block = 22;export const OffscreenComponent = 23;export const LegacyHiddenComponent = 24;
在 react 協調時,beginWork 和 completeWork 等流程時,都會根據 tag
類型的不同,去執行不同的函數處理 fiber 節點。
key
和 type
兩項用于 react diff 過程中確定 fiber 是否可以復用。
key
為用戶定義的唯一值。type
定義與此fiber關聯的功能或類。對于組件,它指向函數或者類本身;對于DOM元素,它指定HTML tag。
stateNode
用于記錄當前fiber所對應的真實dom節點或者當前虛擬組件的實例,這么做的原因第一是為了實現Ref
,第二是為了實現真實 dom的跟蹤。
我們看一下和 fiber 鏈表樹構建相關的 return
、child
和 sibling
幾個字段:
return:指向父 fiber,若沒有父 fiber 則為 null
child: 指向第一個子 fiber,若沒有任何子 fiber 則為 null
sibling:指向下一個兄弟 fiber,若沒有下一個兄弟 fiber 則為 null
通過這幾個字段,各個 fiber 節點構成了 fiber 鏈表樹結構:
首先理解一下 react 中的副作用,舉一個生活中比較通俗的例子:我們感冒了本來吃點藥就沒事了,但是吃了藥發現身體過敏了,而這個“過敏”就是副作用。react 中,我們修改了 state、props、ref 等數據,除了數據改變之外,還會引起 dom 的變化,這種 render 階段不能完成的工作,我們稱之為副作用。相關參考視頻講解:進入學習
react 中通過 flags 記錄每個節點diff后需要變更的狀態,例如 dom 的添加、替換、刪除等等。我們可以看一下源碼中 Flags 枚舉類型:
例如 Deletion
代表更新時要對 dom 進行刪除,Placement
代表要進行添加或者替換等等。
// packages/react-reconciler/src/ReactFiberFlags.jsexport type Flags = number;export const NoFlags = /* */ 0b000000000000000000;export const PerformedWork = /* */ 0b000000000000000001;export const Placement = /* */ 0b000000000000000010;export const Update = /* */ 0b000000000000000100;export const PlacementAndUpdate = /* */ 0b000000000000000110;export const Deletion = /* */ 0b000000000000001000;export const ContentReset = /* */ 0b000000000000010000;export const Callback = /* */ 0b000000000000100000;export const DidCapture = /* */ 0b000000000001000000;export const Ref = /* */ 0b000000000010000000;export const Snapshot = /* */ 0b000000000100000000;export const Passive = /* */ 0b000000001000000000;export const PassiveUnmountPendingDev = /* */ 0b000010000000000000;export const Hydrating = /* */ 0b000000010000000000;export const HydratingAndUpdate = /* */ 0b000000010000000100;export const LifecycleEffectMask = /* */ 0b000000001110100100;export const HostEffectMask = /* */ 0b000000011111111111;export const Incomplete = /* */ 0b000000100000000000;export const ShouldCapture = /* */ 0b000001000000000000;export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;export const PassiveStatic = /* */ 0b001000000000000000;export const BeforeMutationMask = /* */ 0b000000001100001010;export const MutationMask = /* */ 0b000000010010011110;export const LayoutMask = /* */ 0b000000000010100100;export const PassiveMask = /* */ 0b000000001000001000;export const StaticMask = /* */ 0b001000000000000000;export const MountLayoutDev = /* */ 0b010000000000000000;export const MountPassiveDev = /* */ 0b100000000000000000;
在 render 階段時,react 會采用深度優先遍歷,對 fiber 樹進行遍歷,把每一個有副作用的 fiber 篩選出來,最后構建生成一個只帶副作用的 Effect list 鏈表。和該鏈表相關的字段有 firstEffect
、nextEffect
和 lastEffect
:
firstEffect
指向第一個有副作用的 fiber 節點,lastEffect
指向最后一個有副作用的節點,中間的節點全部通過 nextEffect
鏈接,最終形成 Effect 鏈表。
在 commit 階段,React 拿到 Effect list 鏈表中的數據后,根據每一個 fiber 節點的 flags 類型,對相應的 DOM 進行更改。
其他需要重點關注一下的屬性還有 lane
和 alternate
。
lane
代表 react 要執行的 fiber 任務的優先級,通過這個字段,render 階段 react 確定應該優先將哪些任務提交到 commit 階段去執行。
我們看一下源碼中 lane
的枚舉值:
// packages/react-reconciler/src/ReactFiberLane.jsInputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;const NonIdleLanes = /* */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
同 Flags 的枚舉值一樣,Lanes 也是用 31 位的二進制數表示,表示了 31 條賽道,位數越小的賽道,代表的優先級越高。
例如 InputDiscreteHydrationLane
、InputDiscreteLanes
、InputContinuousHydrationLane
等用戶交互引起的更新的優先級較高,DefaultLanes
這種請求數據引起更新的優先級中等,而 OffscreenLane
、IdleLanes
這種優先級較低。
優先級越低的任務,在 render 階段越容易被打斷,commit 執行的時機越靠后。
當 react 的狀態發生更新時,當前頁面所對應的 fiber 樹稱為 current Fiber,同時 react 會根據新的狀態構建一顆新的 fiber 樹,稱為 workInProgress Fiber。current Fiber 中每個 fiber 節點通過 alternate
字段,指向 workInProgress Fiber 中對應的 fiber 節點。同樣 workInProgress Fiber 中的 fiber
節點的 alternate
字段也會指向 current Fiber 中對應的 fiber 節點。
下面我們結合源碼,來看一下實際工作過程中 fiber 樹的構建與更新過程。
react 首次 mount 開始執行時,以 ReactDOM.render
為入口函數,會經過如下一系列的函數調用:ReactDOM.render
——> legacyRenderSubtreeIntoContainer
——> legacyCreateRootFromDOMContainer
——> createLegacyRoot
——> ReactDOMBlockingRoot
——> ReactDOMRoot
——> createRootImpl
——> createContainer
——> createFiberRoot
——> createHostRootFiber
——> createFiber
在 createFiber
函數中,調用 FiberNode
構造函數,創建了 rootFiber,它是 react 應用的根 fiber:
// packages/react-reconciler/src/ReactFiber.old.jsconst createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,): Fiber { return new FiberNode(tag, pendingProps, key, mode);};
在 createFiberRoot
函數中,調用 FiberRootNode
構造函數,創建了 fiberRoot,它指向真實根 dom 節點。
// packages/react-reconciler/src/ReactFiberRoot.old.jsexport function createFiberRoot( containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot { const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any); if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; } const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root;}
另外 createFiberRoot
函數中,還讓 rootFiber 的 stateNode
字段指向了 fiberRoot,fiberRoot 的 current
字段指向了 rootFiber。從而一顆最原始的 fiber 樹根節點就創建完成了:
上面的 rootFiber 和 fiberRoot 創建完成后,react 就會根據 jsx 的內容去創建詳細的 dom 樹了,例如有如下的 jsx:
<div id="root"> <div id="a1"> <div id="b1"> <div id="c1"> <div id="d1"></div> <div id="d2"></div> <div id="d3"></div> </div> <div id="c2"></div> </div> </div></div>
react 對于 fiber 結構的創建和更新,都是采用深度優先遍歷,從 rootFiber(此處對應id為root的節點)開始,首先創建 child a1,然后發現 a1 有子節點 b1,繼續對 b1 進行遍歷,b1 有子節點 c1,再去創建 c1 的子節點 d1、d2、d3,直至發現 d1、d2、d3 都沒有子節點來了,再回去創建 c2.
上面的過程,每個節點開始創建時,執行 beginWork
流程,直至該節點的所有子孫節點都創建(更新)完成后,執行 completeWork
流程,過程的圖示如下:
update 時,react 會根據新的 jsx 內容創建新的 workInProgress fiber,還是通過深度優先遍歷,對發生改變的 fiber 打上不同的 flags
副作用標簽,并通過 firstEffect
、nextEffect
等字段形成 Effect List 鏈表。
例如上面的 jsx 結構,發生了如下的更新:
<div id="root"> <div id="a1"> <div id="b1"> <div id="c1"> <div id="d1"></div>- <div id="d2"></div>- <div id="d3"></div> </div>- <div id="c2"></div>+ <div id="c2">new content</div> </div> </div></div>
react 會根據新的 jsx 解析后的內容,調用 createWorkInProgress
函數創建 workInProgress fiber,對其標記副作用:
// packages/react-reconciler/src/ReactFiber.old.jsexport function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { let workInProgress = current.alternate; if (workInProgress === null) { // 區分 mount 還是 update workInProgress = createFiber( current.tag, pendingProps, current.key, current.mode, ); workInProgress.elementType = current.elementType; workInProgress.type = current.type; workInProgress.stateNode = current.stateNode; if (__DEV__) { workInProgress._debugID = current._debugID; workInProgress._debugSource = current._debugSource; workInProgress._debugOwner = current._debugOwner; workInProgress._debugHookTypes = current._debugHookTypes; } workInProgress.alternate = current; current.alternate = workInProgress; } else { workInProgress.pendingProps = pendingProps; workInProgress.type = current.type; workInProgress.subtreeFlags = NoFlags; workInProgress.deletions = null; if (enableProfilerTimer) { workInProgress.actualDuration = 0; workInProgress.actualStartTime = -1; } } // 重置所有的副作用 workInProgress.flags = current.flags & StaticMask; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedState = current.memoizedState; workInProgress.updateQueue = current.updateQueue; // 克隆依賴 const currentDependencies = current.dependencies; workInProgress.dependencies = currentDependencies === null ? null : { lanes: currentDependencies.lanes, firstContext: currentDependencies.firstContext, }; workInProgress.sibling = current.sibling; workInProgress.index = current.index; workInProgress.ref = current.ref; if (enableProfilerTimer) { workInProgress.selfBaseDuration = current.selfBaseDuration; workInProgress.treeBaseDuration = current.treeBaseDuration; } if (__DEV__) { workInProgress._debugNeedsRemount = current._debugNeedsRemount; switch (workInProgress.tag) { case IndeterminateComponent: case FunctionComponent: case SimpleMemoComponent: workInProgress.type = resolveFunctionForHotReloading(current.type); break; case ClassComponent: workInProgress.type = resolveClassForHotReloading(current.type); break; case ForwardRef: workInProgress.type = resolveForwardRefForHotReloading(current.type); break; default: break; } } return workInProgress;}
最終生成的 workInProgress fiber 圖示如下:
然后如上面所說,current fiber 和 workInProgress fiber 中對應的 alternate 會相互指向,然后 workInProgress fiber 完全創建完成后,fiberRoot 的 current
字段的指向會從 current fiber 中的 rootFiber 改為 workInProgress fiber 中的 rootFiber:
感謝各位的閱讀,以上就是“React Fiber樹是怎么構建與更新的”的內容了,經過本文的學習后,相信大家對React Fiber樹是怎么構建與更新的這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。