本篇文章暫時討論Sync
模式(同步),源碼爲16.9,部分源碼內容不討論(hooks classComponent等等相關的代碼)。javascript
先看一段react的代碼html
function Counter(props) { return ( <div> <div>{props.count}</div> <button onClick={() => { console.log('l am button'); }} > add </button> </div> ) } function App(props) { return <Counter count="12" key="12" />; } ReactDOM.render(<App />, document.getElementById('app'));
jsx語法能夠經過babel
對應的jsx插件須要轉義成可執行的代碼(try it out), 上述代碼<App />
:java
// 轉義後的代碼 function App(props) { return React.createElement(CounterButton, { key: "12" }); } // 結果 { $$typeof: Symbol(react.element), key: null, props: {}, ref: null, type: ƒ App(props), }
傳入ReactDOM.render
函數的三個參數element
、 container
、callback
node
container
的_reactRootContainer
屬性在第一次建立是不存在的,先要建立它react
// ReactDOM.js let rootSibling; while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); }
先將container
即咱們傳入div#app
的全部子節點刪除 獲得的結果:git
// root { _internalRoot: { current: FiberNode, containerInfo: div#app, ... } }
current
指向的是 root fiber節點, containerInfo 執行 dom元素 id爲app的divgithub
unbatchedUpdates數組
接着使用unbatchedUpdates
調用updateContainer
, unbatchedUpdates
來自調度系統ReactFiberWorkLoop
babel
// ReactFiberWorkLoop.js function unbatchedUpdates(fn, a) { const prevExecutionContext = executionContext; executionContext &= ~BatchedContext; executionContext |= LegacyUnbatchedContext; try { return fn(a); } finally { executionContext = prevExecutionContext; if (executionContext === NoContext) { flushSyncCallbackQueue(); } } }
全局變量executionContext
表明當前的執行上下文, 初始化爲 NoContent
app
// ReactFiberWorkLoop.js const NoContext = /* */ 0b000000; const BatchedContext = /* */ 0b000001; const EventContext = /* */ 0b000010; const DiscreteEventContext = /* */ 0b000100; const LegacyUnbatchedContext = /* */ 0b001000; const RenderContext = /* */ 0b010000; const CommitContext = /* */ 0b100000;
executionContext &= ~BatchedContext
表明什麼含義尼?
首先 &
操做當且當兩個位上都爲1的時候返回1,|
只要有一位爲1
,返回1
executionContext
則是這些Context組合的結果:
將當前上下文添加Render
:
executionContext |= RenderContext
判斷當前是否處於Render
階段
executionContext &= RenderContext === NoContext
去除Render
:
executionContext &= ~RenderContext
executionContext &= ~BatchedContext
則表明把當前上下文的BatchedContext
標誌位置爲false,表示當前爲非批量更新
在react源碼中有不少相似的位運算,好比effectTag,workTag。
updateContainer
計算當前時間和當前的過時時間,因本文只討論同步模式因此這裏的expirationTime
爲
// ReactFiberExpirationTime.js const Sync = MAX_SIGNED_31_BIT_INT; // ReactFiberWorkLoop.js function computeExpirationForFiber( currentTime, fiber, suspenseConfig, ) { const mode = fiber.mode if ((mode & BatchedMode) === NoMode) { return Sync } }
expirationTime
越大,表明優先級越高,因此同步模式擁有最高的優先級。
在updateContainerAtExpirationTime
建立於context
相關內容,後續有專門文章介紹context
,這裏先不討論。
scheduleRootUpdate
// ReactFiberReconciler.js function scheduleRootUpdate( current, element, expirationTime, suspenseConfig, callback, ) { const update = createUpdate(expirationTime, suspenseConfig); update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { update.callback = callback; } enqueueUpdate(current, update); scheduleWork(current, expirationTime); return expirationTime; }
建立update
,將callback添加到update上。
{ callback: null expirationTime: 1073741823 next: null nextEffect: null payload: {element: { $$typeof: Symbol(react.element) key: null props: {} ref: null type: ƒ App(props) }} priority: 97 suspenseConfig: null tag: 0 }
再更新添加到root fiber的更新隊列上,指的一提的是這裏的更新隊列updateQueue
也採用了雙緩衝技術,兩條updateQueue
經過alternate
屬性
相互引用。這個鏈表大體爲:
{ baseState: null firstCapturedEffect: null firstCapturedUpdate: null firstEffect: null firstUpdate: update lastCapturedEffect: null lastCapturedUpdate: null lastEffect: null lastUpdate: update }
調用scheduleWork
進入到調度階段。
// ReactFiberWorkLoop.js function scheduleUpdateOnFiber(fiber, expirationTime) { const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime); if (expirationTime === Sync) { if ( (executionContext & LegacyUnbatchedContext) !== NoContext && (executionContext & (RenderContext | CommitContext)) === NoContext ) { let callback = renderRoot(root, Sync, true); while (callback !== null) { callback = callback(true); } } } }
進入調度階段,首先調用markUpdateTimeFromFiberToRoot
將fiber上的更新時間,此時的fiber樹只有一個root fiber光桿司令。
// ReactFiberWorkLoop.js function markUpdateTimeFromFiberToRoot() { if (fiber.expirationTime < expirationTime) { fiber.expirationTime = expirationTime; } ... let alternate = fiber.alternate; let node = fiber.return; let root = null; if (node === null && fiber.tag === HostRoot) { root = fiber.stateNode; } else { ... } return root }
這裏返回的root是個fiberRoot類型的節點。
繼續往下,條件expirationTime === Sync
符合
executionContext & LegacyUnbatchedContext) !== NoContext && executionContext & (RenderContext | CommitContext)) === NoContext
這裏的兩個位運算,在unbatchedUpdates
方法內將初始化的上下文NoContext
添加了LegacyUnbatchedContext
上下文,因此這裏獲得的結果是真。
renderRoot
renderRoot階段只要進行兩部分工做:一個是workLoop循環,即render階段 另外一個爲commitRoot,commit階段
// ReactFiberExpirationTime.js const NoWork = 0 // ReactFiberWorkLoop.js let workInProgressRoot = null let renderExpirationTime = NoWork function renderRoot(root, expirationTime) { ... if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) { prepareFreshStack(root, expirationTime); } ... /* renderRoot-code-branch-01 */ }
此時的 workInProgressRoot
和renderExpirationTime
都處於初始狀態。
function prepareFreshStack(root, expirationTime) { root.finishedWork = null; root.finishedExpirationTime = NoWork; ... workInProgressRoot = root; workInProgress = createWorkInProgress(root.current, null, expirationTime); renderExpirationTime = expirationTime; ... }
prepareFreshStack
顧名思義,準備一個新生的堆棧環境。
首先將finishedWork
相關的變量初始化。
將root
賦給全局變量workInProgressRoot
將expirationTime
賦給renderExpirationTime
爲root.current即root fiber節點建立一個workInProgress
節點,並將該節點賦給全局變量workInProgress
。fiber
節點也是應用了雙緩衝,兩個fiber節點經過alternate
屬性保存了對方的引用 在更新的過程當中操做的是workInProgress節點。調度結束時 workInProgress fiber
會替代current fiber
。
/* renderRoot-code-branch-01 */ if (workInProgress !== null) { const prevExecutionContext = executionContext; executionContext |= RenderContext; /* hooks-related ** start */ let prevDispatcher = ReactCurrentDispatcher.current; if (prevDispatcher === null) { prevDispatcher = ContextOnlyDispatcher; } ReactCurrentDispatcher.current = ContextOnlyDispatcher; /* hooks-related ** end */ /* workLoop */ }
此時的workInProgress
爲剛建立的那個節點。接着爲當前的上下文添加RenderContext
,標誌着進入render階段。hooks-related
這部分代碼是與hooks先關的代碼,在這過程當中用戶調用hooks相關的API都不是在FunctionComponent
的內部,因此都會報錯。
function workLoopSync() { while (workInProgress !== null) { workInProgress = performUnitOfWork(workInProgress); } } /* workLoop */ do { try { if (isSync) { workLoopSync() } } catch (error) { // ... } break } while (true)
workLoop過程是一個遞歸的過程 從root階段向下遍歷到葉子節點,再從葉子節點執行一些遍歷的邏輯最後返回到root節點,此次過程執行beginWork
,completeWork
等操做,
在此過程當中建立fiber節點組裝fiber樹,建立對應的dom節點等等。
文章開始的代碼workLoop過程大體以下:
一個簡單的線上demo,根據代碼模擬workLoop執行過程地址(放在githubpage上的打開速度可能慢一些)
讓咱們開啓workLoop之旅吧!
function performUnitOfWork(unitOfWork) { const current = unitOfWork.alternate ... let next = beginWork(current, unitOfWork, renderExpirationTime) unitOfWork.memoizedProps = unitOfWork.pendingProps if (next === null) { next = completeUnitOfWork(unitOfWork) } return next }
在這個循環過程 beginWork順着element樹的向下深度遍歷 當遍歷到葉子節點時,即next爲null時, completeUnitOfWork則會定位next的值:
固然這兩個過程所得工做不只僅就是這樣。
// ReactFiberBeginWork.js let didReceiveUpdate = false function beginWork( current, workInProgress, renderExpirationTime ) { if (current !== null) { const oldProps = current.memoizedProps const newProps = workInProgress.pendingProps if (oldProps !== newProps || hasLegacyContextChanged()) { didReceiveUpdate = true; } else if (updateExpirationTime < renderExpirationTime) { ... } } else { didReceiveUpdate = true } workInProgress.expirationTime = NoWork; switch (workInProgress.tag) { case HostRoot: { return updateHostRoot(current, workInProgress, renderExpirationTime); } case } }
root fiber是存在current fiber
的,但此時的oldProps
和newProps
都爲null。雖然這裏不討論context
,可是從
if (oldProps !== newProps || hasLegacyContextChanged()) { didReceiveUpdate = true; }
咱們能夠看出舊的context
API的低效。
在進入到beginWork
以前先將expirationTime
置爲NoWork
beginWork HostRoot
root fiber對應的更新爲HostRoot
// ReactFiberBeginWork.js function updateHostRoot(current, workInProgress, renderExpirationTime) { const updateQueue = workInProgress.updateQueue; const nextProps = workInProgress.pendingProps; const prevState = workInProgress.memoizedState; const prevChildren = prevState !== null ? prevState.element : null; processUpdateQueue( workInProgress, updateQueue, nextProps, null, renderExpirationTime, ); const nextState = workInProgress.memoizedState; const nextChildren = nextState.element; if (nextChildren === prevChildren) { ... } const root = workInProgress.stateNode if ((current === null || current.child === null) && root.hydrate) { ... } else { reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); } return workInProgress.child; }
在scheduleRootUpdate
建立的更新隊列咱們建立了一個更新隊列,裏面有一條更新。
processUpdateQueue
對於所作的將隊列清空 將update
的payload
合併到updateQueue
的baseState
屬性 同時添加到workInProgress節點的memoizedState
上
因此nextChildren
就是memoizedState
的element
屬性了。也就是
{ $$typeof: Symbol(react.element) key: null props: {} ref: null type: ƒ App(props) }
接着root.hydrate
這個判斷是服務端渲染相關的代碼,這裏不涉及,因此走另外一個分支
// ReactFiberBeginWork.js function reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime ) { if (current === null) { workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderExpirationTime, ); } else { workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderExpirationTime, ); } }
根據 current 是否存在 走不一樣的分支,mountChildFibers
和mountChildFibers
不一樣在於一個參數傳遞的問題。此時current.child
爲null
// ReactChildFiber.js const reconcileChildFibers = ChildReconciler(true); const mountChildFibers = ChildReconciler(false);
ChildReconciler
ChildReconciler
是一個高階函數,內部許多子方法,依次看來
// ReactChildFiber.js function ChildReconciler(shouldTrackSideEffects) { function reconcileChildFibers( returnFiber, currentFirstChild, newChild, expirationTime ) { // Fragment相關內容 先跳過 const isUnkeyedTopLevelFragment = false const isObject = typeof newChild === 'object' && newChild !== null; if (isObject) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, expirationTime, ), ); } } /** **/ } }
這裏暫不討論 Fragment相關內容 直接將標誌位isUnkeyedTopLevelFragment
置爲假。這裏的newChild對應着 App組件,isObject
爲真,且newChild.$$typeof === REACT_ELEMENT_TYPE
。
reconcileSingleElement placeSingleChild
// ReactChildFiber.js function reconcileSingleElement( returnFiber, currentFirstChild, element, expirationTime ) { const key = element.key let child = currentFirstChild while(child !== null) { ... } if (element.type === REACT_FRAGMENT_TYPE) { ... } else { const created = createFiberFromElement( element, returnFiber.mode, expirationTime, ); // to do // created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; return created; } } function placeSingleChild(newFiber) { if (shouldTrackSideEffects && newFiber.alternate === null) { newFiber.effectTag = Placement; } return newFiber }
App組件對應的 fiber節點在以前並不存在,因此這裏建立fiber節點 並將fiber的父節點設爲 root fiber節點。以後在placeSingleChild
爲fiber的effectTag
打上 Placement
返回到beginWork
的updateHostRoot
, 接着返回workInProgress.child
,返回到completeUnitOfWork
函數內,
next = beginWork() if (next === null) { ... } return next
返回的爲新建立的App對應的 fiber,因此beginWork繼續執行。
回到剛纔的beginWork
。
建立的Function Component組件fiber默認的tag爲IndeterminateComponent,class Component會被指定爲ClassComponent
let fiber; let fiberTag = IndeterminateComponent; let resolvedType = type; if (typeof type === 'function') { if (shouldConstruct(type)) { fiberTag = ClassComponent; ... } else { ... } } else if (typeof type === 'string') { fiberTag = HostComponent; }
回顧一下beginWork
let didReceiveUpdate = false function beginWork() { ... if (current !== null) { ... } else { didReceiveUpdate = false } switch (workInProgress.tag) { case IndeterminateComponent: { return mountIndeterminateComponent( current, workInProgress, workInProgress.type, renderExpirationTime, ); } } }
mountIndeterminateComponent大體代碼:
function mountIndeterminateComponent( _current, workInProgress, Component, renderExpirationTime ) { if (_current !== null) { ... } const props = workInProgress.pendingProps ... let value = renderWithHooks( null, workInProgress, Component, props, context, renderExpirationTime, ); if (typeof value === 'object' && value !== null && typeof value.render === 'function') { ... } else { workInProgress.tag = FunctionComponent; reconcileChildren(null, workInProgress, value, renderExpirationTime); } return workInProgress.child; }
這裏的renderWithHooks
先簡單當作 Component(props)
,後面部分介紹hooks相關代碼。
返回的value爲:
React.createElement(Counter, { count: "12", key: "12" }) // value { $$typeof: Symbol(react.element) key: "12" props: {} ref: null type: ƒ CounterButton(props) }
reconcileChildren
--> mountChildFibers
爲Counter
組件建立fiber與建立App的fiber邏輯基本相同。所不一樣的是effectTag沒有被標記。
beginWork
Counter
, renderWithHooks 返回的是div,接着建立下一次beginWork的fiber。
{ $$typeof: Symbol(react.element) key: null props: {children: Array(2)} ref: null type: "div" }
beginWork: HostComponent
case HostComponent: return updateHostComponent(current, workInProgress, renderExpirationTime);
// ReactDOMHostConfig.js function shouldSetTextContent(type: string, props: Props): boolean { return ( type === 'textarea' || type === 'option' || type === 'noscript' || typeof props.children === 'string' || typeof props.children === 'number' || (typeof props.dangerouslySetInnerHTML === 'object' && props.dangerouslySetInnerHTML !== null && props.dangerouslySetInnerHTML.__html != null) ); } // ReactFiberBeginWork.js function updateHostComponent( current, workInProgress, renderExpirationTime, ) { const type = workInProgress.type const nextProps = workInProgress.pendingProps const prevProps = current !== null ? current.memoizedProps : null let nextChildren = nextProps.children const isDirectTextChild = shouldSetTextContent(type, nextProps) if (isDirectTextChild) { nextChildren = null } else if (...) { ... } reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); return workInProgress.child; }
這裏的pendingProps
,就是div的props 爲 span button的數組。 shouldSetTextContent
則判斷當前元素可不能夠擁有子元素,或者children能夠做爲一個text節點 以後繼續調用 reconcileChildren
--> mountChildFibers
此時nextChildren是一個數組結構 在ReactFiberChild
中reconcileChildFibers
相應的代碼:
if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, expirationTime, ); } function reconcileChildrenArray( returnFiber, currentFirstChild, newChildren, expirationTime, ) { let resultingFirstChild: Fiber | null = null; let previousNewFiber: Fiber | null = null; let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { ... } if (newIdx === newChildren.length) { ... } if (oldFiber === null) { for (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild( returnFiber, newChildren[newIdx], expirationTime, ); if (newFiber === null) { continue; } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } return resultingFirstChild; } }
因爲第一次建立 此時的currentFirstChild
爲null,reconcileChildrenArray
代碼不少,可是第一次用到的很少,主要遍歷children 爲它們建立fiber,並添加到fiber樹上。
最後返回第一個child的fiber 也就是span對應的fiber。
接着對 span進行beginWork
, 此時的isDirectTextChild
標誌位爲true。nextChildren則爲null。reconcileChildFibers
結果返回null。
此時回到workLoop的performUnitOfWork
,由於next爲null,則進行下一步 completeUnitOfWork
。
function completeUnitOfWork(unitOfWork) { workInProgress = unitOfWork do { const current = workInProgress.alternate const returnFiber = workInProgress.return if ((workInProgress.effectTag & Incomplete) === NoEffect) { let next = completeWork(current, workInProgress, renderExpirationTime); if (next !== null) { return null } ... /* completeUnitOfWork-code-01 */ } else { ... } /* completeUnitOfWork-code-02 */ const siblingFiber = workInProgress.sibling; if (siblingFiber !== null) { return siblingFiber; } workInProgress = returnFiber; /* completeUnitOfWork-code-02 */ } while (workProgress !== null) }
此時傳入的unitOfWork爲span對應的fiber。 將全局變量workInProgress
賦值爲unitWork
(workInProgress.effectTag & Incomplete) === NoEffect
顯然爲true。調用completeWork
返回下一次的工做內容
completeWork
function completeWork( current, workInProgress, renderExpirationTime ) { const newProps = workInProgress.pendingProps switch (workInProgress.tag) { ... case HostComponent: { const rootContainerInfo = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode != null) { ... } else { const currentHostContext = getHostContext(); let instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); appendAllChildren(instance, workInProgress, false, false); if ( finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); } workInProgress.stateNode = instance; } } } return null; }
此處的rootContainerInfo
先把他認爲是div#app
,繼續忽略currentHostContext
。建立過程能夠理解爲三步:
先詳細看一下createInstance
實現
// ReactDOMComponentTree.js export function updateFiberProps(node, props) { node[internalEventHandlersKey] = props; } export function precacheFiberNode(hostInst, node) { node[internalInstanceKey] = hostInst; } // ReactDOMHostConfig function createInstance( type, props, rootContainerInstance, hostContext, internalInstanceHandle ) { const domElement: Instance = createElement( type, props, rootContainerInstance, parentNamespace, ); precacheFiberNode(internalInstanceHandle, domElement); updateFiberProps(domElement, props); return domElement; }
createElement
先暫時理解爲 document.createElement precacheFiberNode
則是 將fiber實例添加到dom上。 updateFiberProps
將fiber實例添加到dom上
雖然是同樣將fiber添加到dom上 經過key的命名能夠發現用途不一樣,updateFiberProps
是爲事件系統作準備的。internalInstanceKey
估計就是爲了保持引用,取值判斷等用途
appendAllChildren
這裏先跳過,到complete div的時候具體分析一下。
因爲是第一次渲染也就不存在diff props的過程,這裏的finalizeInitialChildren
的職責也相對簡單些,設置dom元素的一些初始值。在設置初始值的時候對應不一樣的dom元素有特殊的處理,這些部分咱們也先跳過
export function finalizeInitialChildren( domElement: Instance, type: string, props: Props, rootContainerInstance: Container, hostContext: HostContext, ): boolean { setInitialProperties(domElement, type, props, rootContainerInstance); // return shouldAutoFocusHostComponent(type, props); ... } function setInitialProperties( domElement, tag, rawProps, rootContainerElement, ) { ... const isCustomComponentTag = true switch (tag) { ... } setInitialDOMProperties( tag, domElement, rootContainerElement, props, isCustomComponentTag, ); } function setInitialDOMProperties( tag, domElement, rootContainerElement, nextProps, ) { for (const propKey in nextProps) { if (!nextProps.hasOwnProperty(propKey)) { continue; } const nextProp = nextProps[propKey]; if (propKey === STYLE) { ... } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { ... } else if (propKey === CHILDREN) { if (typeof nextProp === 'string') { const canSetTextContent = tag !== 'textarea' || nextProp !== ''; if (canSetTextContent) { setTextContent(domElement, nextProp); } } else if (typeof nextProp === 'number') { setTextContent(domElement, '' + nextProp); } } else if (registrationNameModules.hasOwnProperty(propKey)) { ... } else if (nextProp != null) { setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag); } } }
在設置dom屬性的時候,有幾個注意點 一個是style屬性的設置 最終的style屬性是字符串,而咱們寫的則是屬性名是駝峯命名的對象。感興趣的可自行查看setValueForStyles。
span的children屬性是被當作文字節點設置
// setTextContent.js function(node, text) { if (text) { let firstChild = node.firstChild; if ( firstChild && firstChild === node.lastChild && firstChild.nodeType === TEXT_NODE ) { firstChild.nodeValue = text; return; } } node.textContent = text; }
回到completeWork
,最後將建立的dom添加到fiber的stateNode
屬性上,返回null 結束completeWork
調用
返回到completeUnitOfWork
的/* completeUnitOfWork-code-01 */
/* completeUnitOfWork-code-01 */ if ( returnFiber !== null && (returnFiber.effectTag & Incomplete) === NoEffect ) { if (returnFiber.effect === null) { returnFiber.firstEffect = workInProgress.firstEffect } if (workInProgress.lastEffect !== null) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = workInProgress.firstEffect; } returnFiber.lastEffect = workInProgress.lastEffect; } const effectTag = workInProgress.effectTag; if (effectTag > PerformedWork) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = workInProgress; } else { returnFiber.firstEffect = workInProgress; } returnFiber.lastEffect = workInProgress; } }
將span節點的 effectList歸併到父組件上(但此時span fiber上並無effect), 此時子組件沒有任何effect,且 effectTag 爲 0。
/* completeUnitOfWork-code-02 */ const siblingFiber = workInProgress.sibling; if (siblingFiber !== null) { return siblingFiber; } workInProgress = returnFiber; /* completeUnitOfWork-code-02 */
/* completeUnitOfWork-code-02 */
,若是當前節點有兄弟節點,則返回,沒有則返回父節點繼續 completeWork。
此時span有一個建立了fiber可是沒有進行beginWork的兄弟節點button
。
button節點經歷過beginWork
, completeWork
,又回到了/* completeUnitOfWork-code-02 */
處。button 節點沒有兄弟節點,workInProgress被置爲了 div 節點,進行
div的 completeWork
。
div的completeWork與 span和button不一樣之處在於appendAllChildren
,以前跳過的部分如今分析一下
function appendAllChildren( parent, workInProgress, ) { let node = workInProgress.child; while (node !== null) { if (node.tag === HostComponent || node.tag === HostText) { // condition 01 appendInitialChild(parent, node.stateNode.instance); } else if (...*2) { } else if (node.child !== null) { // condition 03 node.child.return = node; node = node.child; continue; } if (node === workInProgress) { return null } // condition 04 while (node.sibling === null) { if (node.return === null || node.return === workInProgress) { return; } node = node.return; } node.sibling.return = node.return; node = node.sibling; } }
div的child爲 span且知足 condition 01
,將span添加到div上,輪到button fiber 一樣將 button 添加到div 上。condition 04
處 是當前的返回出口:找到最後一個sibling,在向上查找到 div節點 返回。
咱們實際應用中,上述的 div>span-button 算是最簡單操做。有不少想 div 與 span、button 又隔了一層Function/Class Component。此時就須要利用到condition 03
繼續向child查找,查找各個分叉向下距離workInProgress
最近的host節點,將他們添加到workInProgress
對應的dom上,這樣dom樹才能完整構成。
這樣 divcompleteWork
就完成了,繼續到Counter
組件:
Component
組件的completeWork
是直接被break
,因此這裏只須要將effectList歸併到父節點。
由/* completeUnitOfWork-code-02 */
節點到Counter
的returnFiberApp
節點,App節點與其餘節點不一樣的地方在於其effectTag
爲3。這是怎麼來的尼?還記得咱們的 root fiber節點在beginWork
時與其餘節點不一樣的地方在於:它是有 current
節點的,因此做爲children的App,在placeSingleChild
的時候effectTag
被添加了Placement
,在beginWork
的mountIndeterminateComponent
時,Component
組件的effectTag
被添加了PerformedWork
。
迴歸一下/* completeUnitOfWork-code-01 */
處代碼,只有到App知足effectTag > PerformedWork
,在以前出現的 host 節點的effectTag
都爲0,Function
節點都爲 1(PerformedWork
),都不符合添加effect的要求。因此到此時纔有一個effect
,它被添加到了root Fiber上。
root fiber的completeWork
,它的tag
爲 HostRoot
// ReactFiberCompleteWork.js updateHostContainer = function (workInProgress) { // Noop }; case HostRoot: { ... if (current === null || current.child === null) { workInProgress.effectTag &= ~Placement; } // updateHostContainer(workInProgress) }
這裏current.child爲null,由於咱們以前beginWork時,改變的是workInProgress節點,這裏將Placement effectTag
取消。結束 completeWork。
這時咱們已經到達了root節點,作一些收尾工做
// ReactWorkLoop.js function completeUnitOfWork(unitOfWork) { workInProgress = unitOfWork do { } while (workInProgress !== null) if (workInProgressRootExitStatus === RootIncomplete) { workInProgressRootExitStatus = RootCompleted; } return null; }
workLoopSync
結束以後,將執行上下文由RenderContext
重置爲上次的執行環境
root.finishedWork = root.current.alternate; root.finishedExpirationTime = expirationTime;
以後將workLoop
所作的工做添加到root的finishedWork
上
workLoopSync
部分, 也能夠成爲render階段到此結束。回顧一下在此期間所作的主要工做。
繼續回來renderRoot
function commitRoot() { ... workInProgressRoot = null switch (workInProgressRootExitStatus) { case RootComplete: { ... return commitRoot.bind(null, root); } } }
將workInProgressRoot
置爲null,在completeWork時將workInProgressRootExitStatus
置爲了RootCompleted
,以後進入commitRoot階段。
暫不討論優先級調度相關的代碼,完整代碼戳我 這裏當作:
function commitRoot(root) { commitRootImpl.bind(null, root, renderPriorityLevel) if (rootWithPendingPassiveEffects !== null) { flushPassiveEffects(); } return null; }
commitRoot源碼主要內容是以上遍歷effectList
的三個循環,看看他們作了什麼吧
let nextEffect = null function commitRootImpl(root, renderPriorityLevel) { const finishWork = root.finishWork const expirationTime = root.finishedExpirationTime ... root.finishedWork = null; root.finishedExpirationTime = NoWork; let firstEffect if (finishedWork.effectTag > PerformedWork) { // 將自身effect添加到effect list上 ... } if (firstEffect !== null) { const prevExecutionContext = executionContext; executionContext |= CommitContext; do { try { commitBeforeMutationEffects(); } catch (error) { .. } } while (nextEffect !== null) ... ... nextEffect = null; executionContext = prevExecutionContext; } }
先獲取effectList,在render階段生成的effect list並不包含自身的effect,這裏先添加(但此時finishedWork.effectTag其實爲0),獲取完整的effectList。
以後把當前的執行上下文置爲CommitContext
, 正式進入commit階段。
此時effectList
其實就是App節點的workInProgress fiber
。這裏有一個全局變量nextEffect
表示當前正在處理的effect
commitBeforeMutationEffects
function commitBeforeMutationEffects() { while (nextEffect !== null) { if ((nextEffect.effectTag & Snapshot) !== NoEffect) { ... const current = nextEffect.alternate; commitBeforeMutationEffectOnFiber(current, nextEffect); ... } nextEffect = nextEffect.nextEffect; } }
這個App fiber上的effectTag
爲 3 (Placement | Update),這個循環直接跳過了
function commitMutationEffects() { while (nextEffect !== null) { const effectTag = nextEffect.effectTag ... let primaryEffectTag = effectTag & (Placement | Update | Deletion) switch (primaryEffectTag) { ... case PlacementAndUpdate: { commitPlacement(nextEffect) nextEffect.effectTag &= ~Placement; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); } } nextEffect = nextEffect.nextEffect; } }
commitPlacement
commitPlacement
主要是把dom元素添加到對應的父節點上,對於第一次渲染其實也只是將div添加到div#app
上。並將當前的effectTag update
去掉。
commitWork
// ReactFiberCommitWork.js function commitWork(current, finishedWork) { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case MemoComponent: case SimpleMemoComponent: { // Note: We currently never use MountMutation, but useLayout uses // UnmountMutation. commitHookEffectList(UnmountMutation, MountMutation, finishedWork); return; case HostComponent: { ... } } }
這裏commitWork有涉及到hook組件的部分,這裏暫時跳過。
對於 host組件實際上是有先後props diff的部分,這裏是第一次渲染,因此也就不存在,因此這裏也沒有多少第一渲染須要作的工做。
commitLayoutEffects
// ReactFiberWorkLoop.js import { commitLifeCycles as commitLayoutEffectOnFiber } from 'ReactFiberCommitWork' function commitLayoutEffects() { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; if (effectTag & (Update | Callback)) { recordEffect(); const current = nextEffect.alternate; commitLayoutEffectOnFiber( root, current, nextEffect, committedExpirationTime, ); } ... nextEffect = nextEffect.nextEffect } ... }
App fiber上的effectTag如今剩下1(PerformedWork),並不符合因此噹噹循環也跳出。順便一提,若是咱們的ReactDOM.render有callback的話 將會在這裏執行。
三個循環結束以後將nextEffect置爲null;執行上下文變動成以前的執行上下文。
function commitRootImpl() { ... if ((executionContext & LegacyUnbatchedContext) !== NoContext) { return null; } }
如今咱們的執行上下文還剩下在upbatchedUpdate
添加的LegacyUnbatchedContext
,因此這裏直接返回。到這裏咱們第一渲染過程到這也就基本結束了。
總結一下commit工做:
本文在走源碼的時候也有有許多部分沒有涵蓋 或者直接跳過的地方:
本文是筆者跟着源碼debugger寫出來的文章,對於缺失的部分,計劃慢慢會有對應的介紹部分。另外本文屬於流水帳類型的文章,分析部分很是少,忘你們多多包涵、提提意見,你的參與就是個人動力。