React源碼解析系列文章歡迎閱讀:
React16源碼解析(一)- 圖解Fiber架構
React16源碼解析(二)-建立更新
React16源碼解析(三)-ExpirationTime
React16源碼解析(四)-Scheduler
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
React16源碼解析(八)-更新流程提交階段
正在更新中...node
提交階段相比於渲染階段要簡單不少,由於大部分更新的前期操做都在渲染階段作好了。提交階段的主要任務也就是把以前記錄好的更新操做反映到真實的dom上,而且這個過程是不能中斷的。react
一、檢查 finishedWork 是否也有 effect ,若有插入 effect 鏈表中
二、第一次遍歷effect鏈,更新class組件實例上的state,props,執行getSnapshotBeforeUpdate生命週期
三、第二次遍歷effect鏈,不一樣的effectTag,執行不一樣的操做,好比重置文本節點,執行 插入、更新、刪除等的 effect 操做,真正的對 dom 進行修改。
四、第三次遍歷effect鏈,此次遍歷就是作一些收尾工做。執行componentDidMount、componentDidUpdate,更新的回調函數等。
五、作一些 還原變量 等的收尾工做。segmentfault
下面會着重講解effect鏈表的三次遍歷。api
function commitRoot(root: FiberRoot, finishedWork: Fiber): void { isWorking = true; isCommitting = true; // ...... // 檢查 finishedWork 是否也有 effect ,若有插入 effect 鏈表中 let firstEffect; if (finishedWork.effectTag > PerformedWork) { // A fiber's effect list consists only of its children, not itself. So if // the root has an effect, we need to add it to the end of the list. The // resulting list is the set that would belong to the root's parent, if // it had one; that is, all the effects in the tree including the root. if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // There is no effect on the root. firstEffect = finishedWork.firstEffect; } prepareForCommit(root.containerInfo); // Invoke instances of getSnapshotBeforeUpdate before mutation. nextEffect = firstEffect; startCommitSnapshotEffectsTimer(); // 第一次遍歷 while (nextEffect !== null) { let didError = false; let error; commitBeforeMutationLifecycles(); // ...... } // ...... // Commit all the side-effects within a tree. We'll do this in two passes. // The first pass performs all the host insertions, updates, deletions and // ref unmounts. nextEffect = firstEffect; startCommitHostEffectsTimer(); // 第二次遍歷 while (nextEffect !== null) { let didError = false; let error; // ...... commitAllHostEffects(); // ...... } stopCommitHostEffectsTimer(); resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after // the first pass of the commit phase, so that the previous tree is still // current during componentWillUnmount, but before the second pass, so that // the finished work is current during componentDidMount/Update. root.current = finishedWork; // In the second pass we'll perform all life-cycles and ref callbacks. // Life-cycles happen as a separate pass so that all placements, updates, // and deletions in the entire tree have already been invoked. // This pass also triggers any renderer-specific initial effects. nextEffect = firstEffect; startCommitLifeCyclesTimer(); // 第三次遍歷 while (nextEffect !== null) { commitAllLifeCycles(root, committedExpirationTime); } // 下面作一些 還原變量 等的收尾工做 isCommitting = false; isWorking = false; stopCommitLifeCyclesTimer(); stopCommitTimer(); onCommitRoot(finishedWork.stateNode); const updateExpirationTimeAfterCommit = finishedWork.expirationTime; const childExpirationTimeAfterCommit = finishedWork.childExpirationTime; const earliestRemainingTimeAfterCommit = updateExpirationTimeAfterCommit === NoWork || (childExpirationTimeAfterCommit !== NoWork && childExpirationTimeAfterCommit < updateExpirationTimeAfterCommit) ? childExpirationTimeAfterCommit : updateExpirationTimeAfterCommit; if (earliestRemainingTimeAfterCommit === NoWork) { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; } onCommit(root, earliestRemainingTimeAfterCommit); // ...... }
遍歷effect鏈
在這個循環中,組件的state已經更新,可是節點尚未更新。架構
function commitBeforeMutationLifecycles() { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; if (effectTag & Snapshot) { recordEffect(); const current = nextEffect.alternate; // 這裏調用的是下面的方法,名字同樣 commitBeforeMutationLifeCycles(current, nextEffect); } // Don't cleanup effects yet; // This will be done by commitAllLifeCycles() nextEffect = nextEffect.nextEffect; } }
如下方法作的主要事情:
一、在 class 組件中經過 prevProps, prevState 獲取狀態快照,用於 componentDidUpdate 生命週期
二、狀態快照的獲取經過 getSnapshotBeforeUpdate 生命週期執行後的返回值
commitBeforeMutationLifeCycles 中只有在更新任務是 classComponent 時纔有工做app
function commitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber, ): void { switch (finishedWork.tag) { case ClassComponent: { if (finishedWork.effectTag & Snapshot) { if (current !== null) { // 不是初次加載 // 組件初次加載執行 DidMount 生命週期函數不走 DidUpdate 不須要保存快照對象 const prevProps = current.memoizedProps; const prevState = current.memoizedState; startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate'); // 獲得當前class組件的實例 const instance = finishedWork.stateNode; // 更新實例上的props和state instance.props = finishedWork.memoizedProps; instance.state = finishedWork.memoizedState; // 調用getSnapshotBeforeUpdate生命週期方法 const snapshot = instance.getSnapshotBeforeUpdate( prevProps, prevState, ); // 保存到instance.__reactInternalSnapshotBeforeUpdate 給 DidUpdate生命週期方法使用 instance.__reactInternalSnapshotBeforeUpdate = snapshot; stopPhaseTimer(); } } return; } case HostRoot: case HostComponent: case HostText: case HostPortal: case IncompleteClassComponent: // Nothing to do for these component types return; default: { // ...... } } }
一、遍歷effect鏈
二、不一樣的effectTag,執行不一樣的操做,好比重置文本節點,執行 插入、更新、刪除等的 effect 操做,真正的對 dom 進行修改。dom
下面我開始講解三種節點的操做:
一、插入節點 - commitPlacement
二、更新節點 - commitWork
三、刪除節點 - commitDeletionide
function commitAllHostEffects() { // 遍歷effect鏈 while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // 重置文本節點 if (effectTag & ContentReset) { commitResetTextContent(nextEffect); } if (effectTag & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } } // The following switch statement is only concerned about placement, // updates, and deletions. To avoid needing to add a case for every // possible bitmap value, we remove the secondary effects from the // effect tag and switch on that value. let primaryEffectTag = effectTag & (Placement | Update | Deletion); switch (primaryEffectTag) { case Placement: { // 插入節點 commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is inserted, before // any life-cycles like componentDidMount gets called. // TODO: findDOMNode doesn't rely on this any more but isMounted // does and isMounted is deprecated anyway so we should be able // to kill this. // 刪除effectTag nextEffect.effectTag &= ~Placement; break; } case PlacementAndUpdate: { // 插入而且更新 // 插入節點 commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is inserted, before // any life-cycles like componentDidMount gets called. nextEffect.effectTag &= ~Placement; // 更新節點 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Deletion: { // 刪除節點 commitDeletion(nextEffect); break; } } nextEffect = nextEffect.nextEffect; } }
一、找到 finishedWork 的父節點 parentFiber。尋找的是原生的dom節點對應的fiber。若是父級不是原生dom,則繼續往上尋找。
二、獲得parentFiber對應的原生dom節點parent
三、找到插入節點的後一個節點,由於我要插在它前面
四、用insertBefore或者appendChild進行子節點插入操做函數
注:第4步插入操做須要分狀況,好比若是是原生dom節點是直接插入,若是是Class子節點是須要深度優先遍歷子節點進行插入的。oop
function commitPlacement(finishedWork: Fiber): void { if (!supportsMutation) { return; } // Recursively insert all host nodes into the parent. // 找到 finishedWork 的父節點 parentFiber。尋找的是原生的dom節點對應的fiber。若是父級不是原生dom,則繼續往上尋找 // 因此parentFiber只有三種類型的節點:HostComponent、HostRoot、HostPortal const parentFiber = getHostParentFiber(finishedWork); // Note: these two variables *must* always be updated together. let parent; let isContainer; // 獲得原生dom節點 : parent switch (parentFiber.tag) { case HostComponent: parent = parentFiber.stateNode; isContainer = false; break; case HostRoot: parent = parentFiber.stateNode.containerInfo; isContainer = true; break; case HostPortal: parent = parentFiber.stateNode.containerInfo; isContainer = true; break; default: // ...... } if (parentFiber.effectTag & ContentReset) { // Reset the text content of the parent before doing any insertions resetTextContent(parent); // Clear ContentReset from the effect tag parentFiber.effectTag &= ~ContentReset; } // 這裏操做 dom 使用的是 insertBefore 原生api // 因此須要獲得插入節點的後一個節點,由於我要插在它前面 const before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need recurse down its // children to find all the terminal nodes. let node: Fiber = finishedWork; while (true) { // 若是當前節點是原始dom節點就直接進行插入 if (node.tag === HostComponent || node.tag === HostText) { if (before) { // 有before就用insertBefore if (isContainer) { insertInContainerBefore(parent, node.stateNode, before); } else { insertBefore(parent, node.stateNode, before); } } else { // 沒有before就用appendChild if (isContainer) { appendChildToContainer(parent, node.stateNode); } else { appendChild(parent, node.stateNode); } } } else if (node.tag === HostPortal) { // 是HostPortal直接跳過,由於不是插在這裏的 // If the insertion itself is a portal, then we don't want to traverse // down its children. Instead, we'll get insertions from each child in // the portal directly. } else if (node.child !== null) { // 上面條件不知足其實就是class組件 // 查看子節點是否存在,存在的話把子節點進行插入 continue node.child.return = node; node = node.child; continue; } if (node === finishedWork) { // 對finishedWork已經結束 return; } // 其實這是一個深度優先遍歷 // 深度優先遍歷class組件的子節點,把class組件的原生dom節點所有進行插入操做 // 下面是深度優先遍歷的退回過程,若是沒有兄弟節點,就往上退回 while (node.sibling === null) { if (node.return === null || node.return === finishedWork) { return; } node = node.return; } node.sibling.return = node.return; node = node.sibling; } }
getHostSibling:
這個函數實際上是找我要插入的節點(finishedWork)的下一個dom節點,由於我要插在這個節點前面嘛。因此這個函數作的事情就是:剔除掉全部的非原始dom節點,找到我想要的dom節點。
注:HostPortal也是要被剔除的,由於它不是掛載在這個地方的嘛。
function getHostSibling(fiber: Fiber): ?Instance { // We're going to search forward into the tree until we find a sibling host // node. Unfortunately, if multiple insertions are done in a row we have to // search past them. This leads to exponential search for the next sibling. // TODO: Find a more efficient way to do this. let node: Fiber = fiber; // 外層while循環 siblings: while (true) { // If we didn't find anything, let's try the next sibling. // 若是沒有兄弟節點,向上查找父節點,可是這個父節點不能是原生dom節點 while (node.sibling === null) { if (node.return === null || isHostParent(node.return)) { // 若是到了根節點root了 或者 是原生dom節點 返回 null 說明在真實的dom中 插入的這個節點沒有兄弟節點 // If we pop out of the root or hit the parent the fiber we are the // last sibling. return null; } node = node.return; } // 下面是有兄弟節點的狀況 node.sibling.return = node.return; node = node.sibling; // 兄弟節點不是HostComponent也不是HostText while (node.tag !== HostComponent && node.tag !== HostText) { // If it is not host node and, we might have a host node inside it. // Try to search down until we find one. // 兄弟節點也是將要插入的節點,跳過這個節點查找下一個兄弟節點 if (node.effectTag & Placement) { // If we don't have a child, try the siblings instead. continue siblings; } // If we don't have a child, try the siblings instead. // We also skip portals because they are not part of this host tree. // 若是沒有子節點或者是HostPortal也跳過這個節點查找下一個兄弟節點 if (node.child === null || node.tag === HostPortal) { continue siblings; } else { // 不然返回兄弟節點的子節點 node.child.return = node; node = node.child; } } // Check if this host node is stable or about to be placed. // 若是兄弟節點也是新增節點,尋找下一個兄弟節點 // 不然,就找到了! if (!(node.effectTag & Placement)) { // Found it! return node.stateNode; } } }
一、commitWork 只會更新 HostComponent(dom 節點) 和 HostText(文本節點)
二、HostComponent調用commitUpdate更新
三、HostText調用commitTextUpdate更新
function commitWork(current: Fiber | null, finishedWork: Fiber): void { // ...... switch (finishedWork.tag) { case ClassComponent: { return; } case HostComponent: { // 更新dom標籤節點 const instance: Instance = finishedWork.stateNode; if (instance != null) { // Commit the work prepared earlier. // 提取新的props const newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps // as the newProps. The updatePayload will contain the real change in // this case. // 提取老的props const oldProps = current !== null ? current.memoizedProps : newProps; const type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components. // 取出updateQueue const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any); // 清空updateQueue finishedWork.updateQueue = null; if (updatePayload !== null) { // 進行更新 commitUpdate( instance, updatePayload, type, oldProps, newProps, finishedWork, ); } } return; } case HostText: { // 更新文本節點 const textInstance: TextInstance = finishedWork.stateNode; // 提取新老文本內容 const newText: string = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps // as the newProps. The updatePayload will contain the real change in // this case. const oldText: string = current !== null ? current.memoizedProps : newText; commitTextUpdate(textInstance, oldText, newText); return; } case HostRoot: { return; } case Profiler: { return; } case SuspenseComponent: { return; } case IncompleteClassComponent: { return; } default: { // ...... } } }
commitUpdate:
export function commitUpdate( domElement: Instance, updatePayload: Array<mixed>, type: string, oldProps: Props, newProps: Props, internalInstanceHandle: Object, ): void { // Update the props handle so that we know which props are the ones with // with current event handlers. // 這裏只是: domElement[__reactEventHandlers$] = newProps updateFiberProps(domElement, newProps); // Apply the diff to the DOM node. updateProperties(domElement, updatePayload, type, oldProps, newProps); }
updateProperties:
一、針對特殊的標籤有特殊的處理,好比表單
二、調用updateDOMProperties把以前的diff結果應用的真實dom上面
// Apply the diff. export function updateProperties( domElement: Element, updatePayload: Array<any>, tag: string, lastRawProps: Object, nextRawProps: Object, ): void { // Update checked *before* name. // In the middle of an update, it is possible to have multiple checked. // When a checked radio tries to change name, browser makes another radio's checked false. // 針對表單的特殊標籤 if ( tag === 'input' && nextRawProps.type === 'radio' && nextRawProps.name != null ) { ReactDOMInput.updateChecked(domElement, nextRawProps); } // 判斷是不是自定義標籤 即標籤名中有 ‘-’ const wasCustomComponentTag = isCustomComponent(tag, lastRawProps); const isCustomComponentTag = isCustomComponent(tag, nextRawProps); // Apply the diff. // 把以前的diff結果應用的真實dom上面 updateDOMProperties( domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag, ); // TODO: Ensure that an update gets scheduled if any of the special props // changed. // 針對表單作一些特殊的處理 switch (tag) { case 'input': // Update the wrapper around inputs *after* updating props. This has to // happen after `updateDOMProperties`. Otherwise HTML5 input validations // raise warnings and prevent the new value from being assigned. ReactDOMInput.updateWrapper(domElement, nextRawProps); break; case 'textarea': ReactDOMTextarea.updateWrapper(domElement, nextRawProps); break; case 'select': // <select> value update needs to occur after <option> children // reconciliation ReactDOMSelect.postUpdateWrapper(domElement, nextRawProps); break; } }
updateDOMProperties:
一、把咱們以前渲染階段生成的updatePayload: [k1,null,k2,v2,k3,v3]應用到真實的dom上。
二、對一些dom節點上特殊的屬性作特殊的處理,好比style、dangerouslySetInnerHTML等
function updateDOMProperties( domElement: Element, updatePayload: Array<any>, wasCustomComponentTag: boolean, isCustomComponentTag: boolean, ): void { // TODO: Handle wasCustomComponentTag // 遍歷updatePayload for (let i = 0; i < updatePayload.length; i += 2) { // 取出key 和 value const propKey = updatePayload[i]; const propValue = updatePayload[i + 1]; if (propKey === STYLE) { // 若是是樣式更新 CSSPropertyOperations.setValueForStyles(domElement, propValue); } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { // 若是是dangerouslySetInnerHTML setInnerHTML(domElement, propValue); } else if (propKey === CHILDREN) { // 若是是文本 setTextContent(domElement, propValue); } else { // 不然正常的更新節點的屬性 DOMPropertyOperations.setValueForProperty( domElement, propKey, propValue, isCustomComponentTag, ); } } }
commitTextUpdate:
文本節點的更新就很是簡單了
export function commitTextUpdate( textInstance: TextInstance, oldText: string, newText: string, ): void { textInstance.nodeValue = newText; }
在咱們刪除一個節點時候,你可能簡單的想一下,就把一個節點刪除就行了呀。其實沒這麼簡單哦。
刪除一個節點時候,是須要去遍歷整顆子樹的。第一,若是dom節點下面還有class組件,那麼咱們是要調用它的生命週期方法的(componentWillUnmount),第二,若是有HostPortal,那麼還要去刪除HostPortal中的節點,因此咱們必需要遍歷子樹的。這裏的遍歷採用樹的深度優先遍歷。
react用了三個函數用循環+遞歸深度優先遍歷了整顆子樹。
function commitDeletion(current: Fiber): void { // Recursively delete all host nodes from the parent. // Detach refs and call componentWillUnmount() on the whole subtree. unmountHostComponents(current); // ...... detachFiber(current); }
unmountHostComponents:
function unmountHostComponents(current): void { // We only have the top Fiber that was deleted but we need recurse down its // children to find all the terminal nodes. let node: Fiber = current; // Each iteration, currentParent is populated with node's host parent if not // currentParentIsValid. let currentParentIsValid = false; // Note: these two variables *must* always be updated together. let currentParent; let currentParentIsContainer; while (true) { // 找的父節點,這個父節點必定是個dom節點 if (!currentParentIsValid) { let parent = node.return; findParent: while (true) { invariant( parent !== null, 'Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.', ); switch (parent.tag) { case HostComponent: currentParent = parent.stateNode; currentParentIsContainer = false; break findParent; case HostRoot: currentParent = parent.stateNode.containerInfo; currentParentIsContainer = true; break findParent; case HostPortal: currentParent = parent.stateNode.containerInfo; currentParentIsContainer = true; break findParent; } parent = parent.return; } currentParentIsValid = true; } if (node.tag === HostComponent || node.tag === HostText) { // 若是是原生dom節點 commitNestedUnmounts(node); // After all the children have unmounted, it is now safe to remove the // node from the tree. if (currentParentIsContainer) { removeChildFromContainer((currentParent: any), node.stateNode); } else { removeChild((currentParent: any), node.stateNode); } // Don't visit children because we already visited them. } else if (node.tag === HostPortal) { // 若是是HostPortal不會作什麼操做,直接向下遍歷子節點 由於它沒有ref,也沒有生命週期 // When we go into a portal, it becomes the parent to remove from. // We will reassign it back when we pop the portal on the way up. currentParent = node.stateNode.containerInfo; currentParentIsContainer = true; // Visit children because portals might contain host components. if (node.child !== null) { node.child.return = node; node = node.child; continue; } } else { // 到這裏的話,其實就是react組件節點了,調用commitUnmount,這個方法裏會有生命週期的調用,ref的卸載 commitUnmount(node); // Visit children because we may find more host components below. // 繼續遍歷子節點 if (node.child !== null) { node.child.return = node; node = node.child; continue; } } // 整棵樹遍歷完成 if (node === current) { return; } // 若是沒有兄弟節點,深度優先遍歷能夠向父節點返回了 while (node.sibling === null) { if (node.return === null || node.return === current) { return; } node = node.return; if (node.tag === HostPortal) { // When we go out of the portal, we need to restore the parent. // Since we don't keep a stack of them, we will search for it. currentParentIsValid = false; } } // 到這裏表明已經沒有子節點了,就向兄弟節點方向遍歷 node.sibling.return = node.return; node = node.sibling; } }
commitNestedUnmounts:
這個方法使用深度優先遍歷整顆dom節點爲父節點的樹。子節點還有多是react組件或者HostPortal。
這裏對每一個節點都要調用commitUnmount。若是遇到了HostPortal就會中止對它下面的子樹進行遍歷,由於在commitUnmount中會對HostPortal類型有個特殊的處理。下面再看。
function commitNestedUnmounts(root: Fiber): void { // While we're inside a removed host node we don't want to call // removeChild on the inner nodes because they're removed by the top // call anyway. We also want to call componentWillUnmount on all // composites before this host node is removed from the tree. Therefore // we do an inner loop while we're still inside the host node. let node: Fiber = root; while (true) { commitUnmount(node); // Visit children because they may contain more composite or host nodes. // Skip portals because commitUnmount() currently visits them recursively. if ( node.child !== null && // If we use mutation we drill down into portals using commitUnmount above. // If we don't use mutation we drill down into portals here instead. (!supportsMutation || node.tag !== HostPortal) ) { // 若是是HostPortal就直接跳過子樹的遍歷,因此下面不執行 node.child.return = node; node = node.child; continue; } if (node === root) { return; } while (node.sibling === null) { if (node.return === null || node.return === root) { return; } node = node.return; } node.sibling.return = node.return; node = node.sibling; } }
commitUnmount:
這個方法會對不一樣的節點作不一樣的處理,對ClassComponent會執行它的生命週期以及卸載ref,對HostComponent卸載ref,對HostPortal從新調用unmountHostComponents,這裏咱們就明白了爲何上一個方法遇到HostPortal就中止了對它的子樹的遍歷,由於它會從新遞歸調用unmountHostComponents遍歷子樹。
function commitUnmount(current: Fiber): void { onCommitUnmount(current); switch (current.tag) { case ClassComponent: { // 卸載ref safelyDetachRef(current); const instance = current.stateNode; // 執行WillUnmount方法,這個時候真實dom尚未卸載,即將卸載 if (typeof instance.componentWillUnmount === 'function') { safelyCallComponentWillUnmount(current, instance); } return; } case HostComponent: { // 卸載ref safelyDetachRef(current); return; } case HostPortal: { // TODO: this is recursive. // We are also not using this parent because // the portal will get pushed immediately. // 遞歸調用,遍歷子樹 unmountHostComponents(current); // ...... return; } } }
此次遍歷就是作一些收尾工做。
一、首次渲染執行componentDidMount
二、更新渲染執行 componentDidUpdate
三、執行 setState 的 callback 回調函數
四、清空commitUpdateQueue
function commitAllLifeCycles( finishedRoot: FiberRoot, committedExpirationTime: ExpirationTime, ) { // 循環effect鏈 while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // 有更新或者有回調函數 if (effectTag & (Update | Callback)) { recordEffect(); const current = nextEffect.alternate; commitLifeCycles( finishedRoot, current, nextEffect, committedExpirationTime, ); } if (effectTag & Ref) { recordEffect(); commitAttachRef(nextEffect); } const next = nextEffect.nextEffect; // Ensure that we clean these up so that we don't accidentally keep them. // I'm not actually sure this matters because we can't reset firstEffect // and lastEffect since they're on every node, not just the effectful // ones. So we have to clean everything as we reuse nodes anyway. nextEffect.nextEffect = null; // Ensure that we reset the effectTag here so that we can rely on effect // tags to reason about the current life-cycle. nextEffect = next; } }
commitLifeCycles:
這個方法會執行componentDidMount或者componentDidUpdate生命週期方法,最後調用commitUpdateQueue。
function commitLifeCycles( finishedRoot: FiberRoot, current: Fiber | null, finishedWork: Fiber, committedExpirationTime: ExpirationTime, ): void { switch (finishedWork.tag) { case ClassComponent: { const instance = finishedWork.stateNode; if (finishedWork.effectTag & Update) { if (current === null) { // 首次渲染 startPhaseTimer(finishedWork, 'componentDidMount'); instance.props = finishedWork.memoizedProps; instance.state = finishedWork.memoizedState; instance.componentDidMount(); stopPhaseTimer(); } else { // 組件更新 const prevProps = current.memoizedProps; const prevState = current.memoizedState; startPhaseTimer(finishedWork, 'componentDidUpdate'); instance.props = finishedWork.memoizedProps; instance.state = finishedWork.memoizedState; instance.componentDidUpdate( prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate, ); stopPhaseTimer(); } } const updateQueue = finishedWork.updateQueue; if (updateQueue !== null) { instance.props = finishedWork.memoizedProps; instance.state = finishedWork.memoizedState; commitUpdateQueue( finishedWork, updateQueue, instance, committedExpirationTime, ); } return; } case HostRoot: { const updateQueue = finishedWork.updateQueue; if (updateQueue !== null) { let instance = null; if (finishedWork.child !== null) { switch (finishedWork.child.tag) { case HostComponent: instance = getPublicInstance(finishedWork.child.stateNode); break; case ClassComponent: instance = finishedWork.child.stateNode; break; } } // 由於ReactDOM.render也有回調函數 因此也要調用commitUpdateQueue commitUpdateQueue( finishedWork, updateQueue, instance, committedExpirationTime, ); } return; } case HostComponent: { const instance: Instance = finishedWork.stateNode; // Renderers may schedule work to be done after host components are mounted // (eg DOM renderer may schedule auto-focus for inputs and form controls). // These effects should only be committed when components are first mounted, // aka when there is no current/alternate. if (current === null && finishedWork.effectTag & Update) { const type = finishedWork.type; const props = finishedWork.memoizedProps; // 這個函數只是對input標籤有 auto-focus 的狀況進行處理 commitMount(instance, type, props, finishedWork); } return; } case HostText: ...... case HostPortal: ...... case Profiler: ...... case SuspenseComponent: ...... case IncompleteClassComponent:...... default: { // ...... } } }
commitUpdateQueue:
一、調用commitUpdateEffects
二、清空commitUpdateQueue
export function commitUpdateQueue<State>( finishedWork: Fiber, finishedQueue: UpdateQueue<State>, instance: any, renderExpirationTime: ExpirationTime, ): void { // ...... // Commit the effects commitUpdateEffects(finishedQueue.firstEffect, instance); // 清空commitUpdateQueue finishedQueue.firstEffect = finishedQueue.lastEffect = null; // ...... }
commitUpdateEffects:
調用effect上的回調函數。
function commitUpdateEffects<State>( effect: Update<State> | null, instance: any, ): void { while (effect !== null) { const callback = effect.callback; if (callback !== null) { // 調用回調函數,也就是setState的回調函數 effect.callback = null; callCallback(callback, instance); } effect = effect.nextEffect; } }
文章若有不妥,歡迎指正~