react源码中的fiber架构
先看一下FiberNode在源碼中的樣子
FiberNode
// packages/react-reconciler/src/ReactFiber.old.js function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) {// Instancethis.tag = tag;this.key = key;this.elementType = null;this.type = null;this.stateNode = null;// Fiberthis.return = null;this.child = null;this.sibling = null;this.index = 0;this.ref = null;this.pendingProps = pendingProps;this.memoizedProps = null;this.updateQueue = null;this.memoizedState = null;this.dependencies = null;this.mode = mode;// Effectsthis.flags = NoFlags;this.nextEffect = null;this.firstEffect = null;this.lastEffect = null;this.lanes = NoLanes;this.childLanes = NoLanes;this.alternate = null;if (enableProfilerTimer) {// Note: The following is done to avoid a v8 performance cliff.//// Initializing the fields below to smis and later updating them with// double values will cause Fibers to end up having separate shapes.// This behavior/bug has something to do with Object.preventExtension().// Fortunately this only impacts DEV builds.// Unfortunately it makes React unusably slow for some applications.// To work around this, initialize the fields below with doubles.//// Learn more about this here:// https://github.com/facebook/react/issues/14365// https://bugs.chromium.org/p/v8/issues/detail?id=8538this.actualDuration = Number.NaN;this.actualStartTime = Number.NaN;this.selfBaseDuration = Number.NaN;this.treeBaseDuration = Number.NaN;// It's okay to replace the initial doubles with smis after initialization.// This won't trigger the performance cliff mentioned above,// and it simplifies other profiler code (including DevTools).this.actualDuration = 0;this.actualStartTime = -1;this.selfBaseDuration = 0;this.treeBaseDuration = 0;}if (__DEV__) {// This isn't directly used but is handy for debugging internals:...} }- 我們看FiberNode這個構造函數里面只是賦值,我們再找一下鏈路上的Fiber,我們發現在函數createFiber的返回值類型里面出現了Fiber類型,所以
相關參考視頻講解:進入學習
- 整個fiber架構看起來可以分為dom信息、副作用、優先級、鏈表樹等幾個模塊,那我們依次來拆分一下
dom信息節點
tag: WorkTag
我們看到這個`tag`為`WorkTag`類型,用來區分`React`組件的類型 // packages/react-reconciler/src/ReactWorkTags.js export type WorkTag =| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20| 21| 22| 23| 24;export const FunctionComponent = 0; export const ClassComponent = 1; export const IndeterminateComponent = 2; // Before we know whether it is function or class export 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;上述代碼,區分了組件的類型,在后期協調階段beginWork、completeWork的流程里根據不同的類型組件去做不同的fiber節點的處理
key: null | string、type: any
key為唯一值,type為與fiber關聯的節點類型,都用于beginWork流程里面的reconcileChildren流程elementType
元素類型stateNode
- stateNode 用于記錄當前 fiber 所對應的真實 dom 節點或者當前虛擬組件的實例。 - 便于實現Ref - 便于追蹤Rdomfiber 鏈表樹
fiber鏈表樹里面有四個字段return、child、sibling、index
- return:指向父節點,沒有父節點則為null。
- child:指向下一個子節點,沒有下一個子節點則為null。
- sibling:指向兄弟節點,沒有下一個兄弟節點則為null。
- index:父fiber下面的子fiber下標
通過這些字段那么我們可以形成一個閉環鏈表,舉個栗子。
根據上面的代碼所對應的fiber鏈表樹結構就是:
副作用相關
所謂副作用就是一套流程中我們不期望發生的情況。舉個通俗的例子就是我們生活中去學游泳,在學會游泳的過程中嗆了幾口水,這個嗆了幾口水相對于成功學會游泳來說就是副作用,回歸到react代碼中,我們通過某些手段去修改props、state等數據,數據修改完畢之后,但是同時引起了dom不必要的變化,那么這個變化就是副作用,當然這個副作用是必然存在的,就像游泳一樣,必然會嗆幾口水,哈哈。
flags: Flags
記錄當前節點通過`reconcileChildren`之后的的副作用,如插入,刪除等例如Placement,表示插入,也叫新增。Deletion表示刪除,Update表示更新
export type Flags = number;// Don't change these two values. They're used by React Dev Tools. export const NoFlags = /* */ 0b000000000000000000; export const PerformedWork = /* */ 0b000000000000000001;// You can change the rest (and add more). 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; // TODO (effects) Remove this bit once the new reconciler is synced to the old. export const PassiveUnmountPendingDev = /* */ 0b000010000000000000; export const Hydrating = /* */ 0b000000010000000000; export const HydratingAndUpdate = /* */ 0b000000010000000100;// Passive & Update & Callback & Ref & Snapshot export const LifecycleEffectMask = /* */ 0b000000001110100100;// Union of all host effects export const HostEffectMask = /* */ 0b000000011111111111;// These are not really side effects, but we still reuse this field. export const Incomplete = /* */ 0b000000100000000000; export const ShouldCapture = /* */ 0b000001000000000000; export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;// Static tags describe aspects of a fiber that are not specific to a render, // e.g. a fiber uses a passive effect (even if there are no updates on this particular render). // This enables us to defer more work in the unmount case, // since we can defer traversing the tree during layout to look for Passive effects, // and instead rely on the static flag as a signal that there may be cleanup work. export const PassiveStatic = /* */ 0b001000000000000000;// Union of side effect groupings as pertains to subtreeFlags export const BeforeMutationMask = /* */ 0b000000001100001010; export const MutationMask = /* */ 0b000000010010011110; export const LayoutMask = /* */ 0b000000000010100100; export const PassiveMask = /* */ 0b000000001000001000;// Union of tags that don't get reset on clones. // This allows certain concepts to persist without recalculting them, // e.g. whether a subtree contains passive effects or portals. export const StaticMask = /* */ 0b001000000000000000;// These flags allow us to traverse to fibers that have effects on mount // without traversing the entire tree after every commit for // double invoking export const MountLayoutDev = /* */ 0b010000000000000000; export const MountPassiveDev = /* */ 0b100000000000000000;當然副作用不僅僅只是一個,所以React中在render階段中采用的是深度遍歷的策略去找出當前fiber樹中所有的副作用,并維護一個副作用鏈表EffectList,與鏈表相關的字段還有firstEffect、nextEffect 和 lastEffect 我們來畫一張圖來簡略示意一下。
解讀一下就是,fristEffect指向第一個有副作用的fiber節點,lastEffect指向最后一個具有副作用的fiber節點,中間都是用nextEffect鏈接,這樣組成了一個單向鏈表。
render階段里面這一段處理就完了,在后面的commit階段里面,React會根據EffectList里面fiber節點的副作用,會對應的處理相應的DOM,然后生成無副作用的虛擬節點,進行真實dom的創建。
優先級相關
當然React作為一個龐大的框架,肯定有自己的一套關于渲染的優先級機制,不然全都是一股腦按部就班的走,那肯定不行噠。
那么優先級我們就要關注一下lane與alternate,React中每個fiber任務都有自己的lane(執行優先級),這樣在render階段react才知道,應該優先把哪個fiber任務提交到commit階段去執行。而alternate是在render階段中用來做為指針的,什么意思?React在狀態發生改變的時候,就會根據當前的頁面結構,生成兩棵fiber樹,一棵老的稱之為current Fiber,而另一棵將要生成新的頁面的樹叫做workInProgress Fiber,而alternate作為指針,就是把current Fiber中的每一個節點指向workInProgress Fiber中的每一個節點。同樣的他也會從workInProgress Fiber中指向 current Fiber
我們了解到了alternate,那就來說一說這個lane吧。
// packages/react-reconciler/src/ReactFiberLane.js export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000; export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: 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;可見這個lane也是用31位二進制表示的唯一值,來進行優先級的判定的,并且位數越低,則優先級越大。
Props && State相關
pendingProps: any
本次渲染需要使用的 propsmemoizedProps: any
上次渲染使用的 propsupdateQueue: mixed
用于狀態更新、回調函數、DOM更新的隊列memoizedState: any
上次渲染后的 state 狀態dependencies: Dependencies | null
contexts、events 等依賴Fiber樹的創建與更新的流程
上面一部分講了React Fiber的基本架構,從真實dom信息、副作用、優先級等方面看了一下,為后面的render階段的協調與調度以及commit階段打下基礎,那么接下來我們去探討一下new FiberNode之后得到的什么樣的rootFiber。
我們在第二章節里面提到了整個的創建過程# React源碼解析系列(二) – 初始化組件的創建更新流程,那么這里深入探討一下createFiber,在這個函數里面new FiberNode,創建了rootFiber,他也就是整個React應用的的根fiber。并且在createFiberRoot里面new FiberRootNode,創建了fiberRoot,它便是指向真實dom的根節點。所以在# React源碼解析系列(二) – 初始化組件的創建更新流程中我強調了root.current、uninitializedFiber.stateNode這兩個東西,也就是這里說的rootFiber的stateNode字段指向了 fiberRoot,并且fiberRoot的current指向了rootFiber,具體的示例圖如下:
所以這里就完成了fiber樹根節點的創建了。
拿到了上面創建完成的rootFiber和fiberRoot之后那么我們接下來就是去根據我們的組件jsx去創建詳細的dom樹了,舉個例子:
<div className='box'><h1 className='title' style={{'color':'red'}}>React源碼解析</h1><ul><li>第一章</li><li>第二章</li><li>第三章</li><li>第四章</li></ul> </div>現有上面的jsx,那么我們創建dom樹的形式是深度優先遍歷,已beginwork和completework表示一個節點的創建過程,流程如下:
上面的圖說明了,在初始化的時候我們的dom樹是怎么被創建出來的,那么在狀態發生改變的時候,我們會根據當前新的jsx內容創建新的workInProgress fiber,我們以新的jsx為例:
<div className='box'> <h1 className='title' style={{'color':'red'}}>React源碼解析</h1> + <h1 className='title' style={{'color':'red'}}>React源碼解析系列</h1><ul><li>第一章</li><li>第二章</li><li>第三章</li> - <li>第四章</li></ul> + <p>總結</p> </div>上面的jsx表示,更改了h1的內容,刪除了第四章,增加了總結這幾個操作,那么react根據當前新的jsx調用createWorkInProgress方法創建workInProgress fiber,那么我們先去看一下createWorkInProgress的源碼實現。
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {let workInProgress = current.alternate;if (workInProgress === null) { // null為初始化,否為update// We use a double buffering pooling technique because we know that we'll// only ever need at most two versions of a tree. We pool the "other" unused// node that we're free to reuse. This is lazily created to avoid allocating// extra objects for things that are never updated. It also allow us to// reclaim the extra memory if needed.workInProgress = createFiber(current.tag,pendingProps,current.key,current.mode,);workInProgress.elementType = current.elementType;workInProgress.type = current.type;workInProgress.stateNode = current.stateNode;if (__DEV__) {// DEV-only fieldsworkInProgress._debugID = current._debugID;workInProgress._debugSource = current._debugSource;workInProgress._debugOwner = current._debugOwner;workInProgress._debugHookTypes = current._debugHookTypes;}// current 指向 workInProgressworkInProgress.alternate = current;// workInProgress 指向 currentcurrent.alternate = workInProgress;} else {// 上一次的propsworkInProgress.pendingProps = pendingProps;// Needed because Blocks store data on type.workInProgress.type = current.type;// We already have an alternate.// Reset the effect tag.//清除flagsworkInProgress.flags = NoFlags;// The effect list is no longer valid.workInProgress.nextEffect = null;workInProgress.firstEffect = null;workInProgress.lastEffect = null;if (enableProfilerTimer) {// We intentionally reset, rather than copy, actualDuration & actualStartTime.// This prevents time from endlessly accumulating in new commits.// This has the downside of resetting values for different priority renders,// But works for yielding (the common case) and should support resuming.workInProgress.actualDuration = 0;workInProgress.actualStartTime = -1;}}// 綁定掛載的子fiber節點優先級、狀態、propsworkInProgress.childLanes = current.childLanes;workInProgress.lanes = current.lanes;workInProgress.child = current.child;workInProgress.memoizedProps = current.memoizedProps;workInProgress.memoizedState = current.memoizedState;workInProgress.updateQueue = current.updateQueue;// Clone the dependencies object. This is mutated during the render phase, so// it cannot be shared with the current fiber.const currentDependencies = current.dependencies;workInProgress.dependencies =currentDependencies === null? null: {lanes: currentDependencies.lanes,firstContext: currentDependencies.firstContext,};// These will be overridden during the parent's reconciliationworkInProgress.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; }并且為其標記副作用,具體如下:
而前面所說的alternate在這里相互指向,其實也就是在reconciler階段起到了復用節點的作用,因為我們所說的current fiber或者是workInProgress fiber都是視圖的產物,是可以在"新"與"老"之間轉換的。
為什么會出現Fiber架構呢?
相信在座的各位寫React的同學出去面試,面試官總會問:”請問你知道React Fiber架構嗎?請你說說Fiber架構吧“
為什么會出現?通過上面的React Fiber架構的講解,我們可以get到幾個點,那就是fiber針對每一個fiber節點都會有一套自己的獨立的beginwork和completework,并且能夠在每一個具有副作用的節點上進行打標處理,而不是直接變更。而且生成的current fiber與workIProgress fiber可以相互轉換,這里間接地可以稱之為緩存吧。對比與以前的React應用來講,以前的React應用是根據執行生命周期、diff、dom的更新一套流程同步走的,一套流程下來,不能中斷,而且每一次的更新都是從根節點出發向下遍歷的,我們可以設想一下處理龐大的結構的時候,那將是不可想象的性能開銷,處理長時間任務耗時更長,更重要的是用戶的交互,事件得不到及時響應,用戶體驗非常的差。
但是fiber這種結構,我們說的是一種時間分片的概念,通過時間分片把長任務,分成一個個獨立的小單元去執行,返回。這樣子就不會讓js線程被React應用獨占,能有有空余去處理其他優先級較高的任務,任務得到了相應并且執行,當然了這種情況下頁面就不會顯得卡頓了。
所以總結來說就是React Fiber給我們提供了一種協調,調度,暫停,中止,調優的方式去更好的處理React應用與瀏覽器的工作,保證了頁面的性能與流暢度
總結
這一章講述了整個的fiber架構與fiber樹的創建與更新,那么這里從React應用的初始化掛載到React更新就形成了一部分的閉環完結,之后我們便是沿著流程走到了updateContainer更新這里
總結
以上是生活随笔為你收集整理的react源码中的fiber架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 酷狗繁星主播怎么pk连麦?怎么第一时间去
- 下一篇: MySql desc 的三种用法