React源碼解析系列文章歡迎閱讀:
React16源碼解析(一)- 圖解Fiber架構
React16源碼解析(二)-建立更新
React16源碼解析(三)-ExpirationTime
React16源碼解析(四)-Scheduler
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
React16源碼解析(八)-更新流程提交階段
正在更新中...node
接着上篇文章,在beginWork中,經過workInProgress.tag判斷當前是什麼類型的節點而調用不一樣的更新函數。這篇文章講解各類類型的組件的更新過程。react
在beginWork中:算法
case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); }
一、調用函數,即業務中寫好的函數組件。獲得一個ReactElement,即nextChildren
二、調用reconcileChildren,第一個參數current=當前fiber節點。第二個參數workInProgress=須要更新的fiber節點。第三個參數nextChildren,上面函數的返回值。
三、上面調用的reconcileChildren方法,實際上是改變了workInProgress.child。
四、返回workInProgress.child。segmentfault
function updateFunctionComponent( current, workInProgress, Component, nextProps: any, renderExpirationTime, ) { // ...... let nextChildren; // Component 組件方法,這裏就是咱們聲明組件的方式 function(props, context) {} nextChildren = Component(nextProps, context); // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; // 把 nextChildren 這些 ReactElement 變成 Fiber 對象, 在 workInProgress.child 掛載 fiber reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); return workInProgress.child; }
一、判斷當前節點是否爲null,若是是第一次渲染,current=null,則調用mountChildFibers,函數返回值賦值給workInProgress.child。
二、current!==null,說明是更新節點。調用reconcileChildFibers(workInProgress,current.child,nextChildren),函數返回值賦值給workInProgress.child。數組
export function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderExpirationTime: ExpirationTime, ) { if (current === null) { // 第一次渲染組件 // If this is a fresh new component that hasn't been rendered yet, we // won't update its child set by applying minimal side-effects. Instead, // we will add them all to the child before it gets rendered. That means // we can optimize this reconciliation pass by not tracking side-effects. workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderExpirationTime, ); } else { // 更新組件 // If the current child is the same as the work in progress, it means that // we haven't yet started any work on these children. Therefore, we use // the clone algorithm to create a copy of all the current children. // If we had any progressed work already, that is invalid at this point so // let's throw it out. workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderExpirationTime, ); } }
咱們查找代碼發現,reconcileChildFibers和mountChildFibers實際上是同一個方法(ChildReconciler),初始化時傳入了不一樣的參數。前者傳入true,後者傳入flase架構
export const reconcileChildFibers = ChildReconciler(true); export const mountChildFibers = ChildReconciler(false);
一、你會發現,這個一個無敵長的巨型方法。
二、看到這個方法的最後return reconcileChildFibers;
三、往上面找到這個方法reconcileChildFibers,就在return 的上面app
function ChildReconciler(shouldTrackSideEffects) { // ...... function reconcileChildFibers(......){ // ...... } return reconcileChildFibers; }
一、這個方法第三個參數newChild即爲咱們調用函數組件返回的新的child。
二、判斷newChild是什麼類型的節點,不一樣類型對應不一樣的操做。好比newChild.$$typeof=REACT_ELEMENT_TYPE,則return placeSingleChild(reconcileSingleElement())。若是是數組,調用reconcileChildrenArray()進行調和,還有多是REACT_PORTAL_TYPE、string、number、Iterator等。
三、若是這個newChild上面的都不符合,但又是個對象但又不是null,那麼就是一個非法的定義了。就throwOnInvalidObjectType拋出錯誤。
四、最後,調用deleteRemainingChildren刪除掉全部子節點。由於到了最後,只有可能newChild === null,說明新的更新清空掉了全部子節點。less
注:deleteRemainingChildren 這個函數裏面調用deleteChild逐個刪除,但刪除子節點並非真的刪除這個對象,而是經過 firstEffect、lastEffect、nextEffect 屬性來維護一個 EffectList(鏈表結構),經過 effectTag 標記當前刪除操做,這些信息都會在 commit 階段使用到dom
// This API will tag the children with the side-effect of the reconciliation // itself. They will be added to the side-effect list as we pass through the // children and the parent. /* reconcileChildFibers函數中主要是根據newChild類型,調用不一樣的Diff算法: 一、單個元素,調用reconcileSingleElement 二、單個Portal元素,調用reconcileSinglePortal 三、string或者number,調用reconcileSingleTextNode 四、array(React 16 新特性),調用reconcileChildrenArray 前三種狀況,在新子節點上添加 effectTag:Placement,標記爲更新操做,而這些操做的標記,將用於commit階段。下面以單個元素爲例,講講具體的Diff算法 */ function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, expirationTime: ExpirationTime, ): Fiber | null { // This function is not recursive. // If the top level item is an array, we treat it as a set of children, // not as a fragment. Nested arrays on the other hand will be treated as // fragment nodes. Recursion happens at the normal flow. // Handle top level unkeyed fragments as if they were arrays. // This leads to an ambiguity between <>{[...]}</> and <>...</>. // We treat the ambiguous cases above the same. // 判斷是否爲 fragment,是的話取 fragment 的 children // fragment標籤沒有意義 只渲染children const isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null; if (isUnkeyedTopLevelFragment) { newChild = newChild.props.children; } // Handle object types // 接下來開始判斷類型 const isObject = typeof newChild === 'object' && newChild !== null; // ReactElment 或者 ReactPortal if (isObject) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, expirationTime, ), ); case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, expirationTime, ), ); } } // 文本 if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild( reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, expirationTime, ), ); } // 數組 if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, expirationTime, ); } // iterator類型 if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, expirationTime, ); } // 拋錯 if (isObject) { throwOnInvalidObjectType(returnFiber, newChild); } // 錯誤處理 if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) { // ...... } // Remaining cases are all treated as empty. // 到這裏說明返回值爲 null,刪除全部的 children return deleteRemainingChildren(returnFiber, currentFirstChild); }
更新渲染時 placeSingleChild 會把新建立的 fiber 節點標記爲 Placement, 待到提交階段處理,其中 ReactElement, Portal, TextNode 三種類型的節點須要進行處理異步
function placeSingleChild(newFiber: Fiber): Fiber { // This is simpler for the single child case. We only need to do a // placement for inserting new children. if (shouldTrackSideEffects && newFiber.alternate === null) { newFiber.effectTag = Placement; } return newFiber; }
調和單個子節點。
一、經過key判斷是否節點是否能夠複用
二、根據節點的不一樣建立不一樣的fiber對象,若是是REACT_FRAGMENT_TYPE類型經過createFiberFromFragment建立fiber對象,其餘類型經過createFiberFromElement建立fiber對象。
三、createFiberFromElement -> createFiberFromTypeAndProps -> createFiber
注:這裏調和單個子節點, 若是 key 不存在爲 null 咱們也認爲他是相等的,判斷 type 和 elementType 來看他們是否一是個組件函數
function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement, expirationTime: ExpirationTime, ): Fiber { const key = element.key; let child = currentFirstChild; while (child !== null) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. // 判斷key是否相等 if (child.key === key) { if ( child.tag === Fragment ? element.type === REACT_FRAGMENT_TYPE : child.elementType === element.type ) { // key相等且type相等,刪除舊子節點的兄弟節點,複用舊節點並返回 deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber( child, element.type === REACT_FRAGMENT_TYPE ? element.props.children : element.props, expirationTime, ); existing.ref = coerceRef(returnFiber, child, element); existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } else { // key相等但type不相等,刪除舊子節點及兄弟節點,跳出循環 deleteRemainingChildren(returnFiber, child); break; } } else { // key不相等,刪除此舊子節點,繼續循環 deleteChild(returnFiber, child); } // 繼續遍歷此舊子節點的兄弟節點,找尋複用節點 child = child.sibling; } // 不能複用,則直接新建Fiber實例,並返回 if (element.type === REACT_FRAGMENT_TYPE) { const created = createFiberFromFragment( element.props.children, returnFiber.mode, expirationTime, element.key, ); created.return = returnFiber; return created; } else { const created = createFiberFromElement( element, returnFiber.mode, expirationTime, ); created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; return created; } }
deleteChild標記刪除:
這裏不是真正的刪除,把childToDelete加入到Effect鏈表,記錄effectTag爲Deletion
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void { if (!shouldTrackSideEffects) { // Noop. return; } // Deletions are added in reversed order so we add it to the front. // At this point, the return fiber's effect list is empty except for // deletions, so we can just append the deletion to the list. The remaining // effects aren't added until the complete phase. Once we implement // resuming, this may not be true. // 找到父組件中須要更新的最後一個子組件 const last = returnFiber.lastEffect; // 判斷鏈表是否存在 // 這個鏈表的目的就是把該父節點上的全部須要更新的子節點經過鏈表連接起來 // 而後下次真正須要更新的時候只須要遍歷鏈表便可 if (last !== null) { last.nextEffect = childToDelete; returnFiber.lastEffect = childToDelete; } else { returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; } childToDelete.nextEffect = null; childToDelete.effectTag = Deletion; }
deleteRemainingChildren刪除兄弟節點:
一個個找到兄弟節點deleteChild。
function deleteRemainingChildren( returnFiber: Fiber, currentFirstChild: Fiber | null, ): null { if (!shouldTrackSideEffects) { // Noop. return null; } // TODO: For the shouldClone case, this could be micro-optimized a bit by // assuming that after the first child we've already added everything. let childToDelete = currentFirstChild; while (childToDelete !== null) { deleteChild(returnFiber, childToDelete); childToDelete = childToDelete.sibling; } return null; }
一、用一個循環相同位置進行比較,找到第一個不可複用的節點爲止,其中updateSlot函數用來判斷新老節點是否能夠複用
二、新節點已經遍歷完畢,直接把剩下的老節點刪除了就好了
三、老節點已經遍歷完畢,根據剩餘新的節點直接建立 Fiber
四、移動的狀況下進行節點複用:
把全部老數組元素按 key 或者是 index 放 Map 裏
遍歷剩下的 newChildren,找到 Map 裏面能夠複用的節點,若是找不到就建立
function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, newChildren: Array<*>, expirationTime: ExpirationTime, ): Fiber | null { // ...... let resultingFirstChild: Fiber | null = null; let previousNewFiber: Fiber | null = null; let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; /** 一、用一個循環相同位置進行比較,找到第一個不可複用的節點爲止,其中updateSlot函數用來判斷新老節點是否能夠複用 */ for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } // 用於判斷是否能複用 根據 newChild 的類型和 oldChild.key 進行判斷操做 const newFiber = updateSlot( returnFiber, oldFiber, newChildren[newIdx], expirationTime, ); // 不能複用 if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need // a better way to communicate whether this was a miss or null, // boolean, undefined, etc. if (oldFiber === null) { oldFiber = nextOldFiber; } // 跳出遍歷 break; } // 接下來都是能夠複用 fiber 的邏輯 // shouldTrackSideEffects 表明更新組件 // 若是須要追蹤反作用而且是從新建立了一個 fiber 的狀況 // 那麼會把 oldFiber 刪掉 if (shouldTrackSideEffects) { if (oldFiber && newFiber.alternate === null) { // We matched the slot, but we didn't reuse the existing fiber, so we // need to delete the existing child. deleteChild(returnFiber, oldFiber); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. // 是第一個節點 resultingFirstChild = newFiber; } else { // TODO: Defer siblings if we're not at the right index for this slot. // I.e. if we had null values before, then we want to defer this // for each null value. However, we also don't want to call updateSlot // with the previous one. // 鏈表鏈接新的 fiber 節點 previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } // 二、新節點已經遍歷完畢,直接把剩下的老節點刪除了就好了 if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } // 三、老節點已經遍歷完畢,根據剩餘新的節點直接建立 Fiber if (oldFiber === null) { // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild( returnFiber, newChildren[newIdx], expirationTime, ); if (!newFiber) { 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; } // Add all children to a key map for quick lookups. // 把全部老數組元素按 key 或者是 index 放 Map 裏 const existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. // 遍歷剩下的 newChildren,找到 Map 裏面能夠複用的節點,若是找不到就建立 for (; newIdx < newChildren.length; newIdx++) { const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], expirationTime, ); if (newFiber) { if (shouldTrackSideEffects) { if (newFiber.alternate !== null) { // The new fiber is a work in progress, but if there exists a // current, that means that we reused the fiber. We need to delete // it from the child list so that we don't add it to the deletion // list. existingChildren.delete( newFiber.key === null ? newIdx : newFiber.key, ); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } } // 把不能複用的子節點都刪了 if (shouldTrackSideEffects) { // Any existing children that weren't consumed above were deleted. We need // to add them to the deletion list. existingChildren.forEach(child => deleteChild(returnFiber, child)); } return resultingFirstChild; }
在beginWork中:
case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); }
這個函數的做用是對未初始化的類組件進行初始化,對已經初始化的組件更新重用。
一、若是還沒建立實例,初始化,說明是第一次渲染(instance === null)
調用constructClassInstance,執行構造函數,生成實例instance
而後在調用mountClassInstance,掛載實例,主要工做是更新instance.state,而且執行一些生命週期
二、渲染被中斷 instance !== null, current === null
調用resumeMountClassInstance 複用實例但仍是調用首次渲染的生命週期,這個函數若是反覆false則組件無需更新
三、更新渲染 instance !== null, current !== null
調用updateClassInstance,調用 didUpdate 和 componentWillReceiveProp 生命週期,這個函數若是反覆false則組件無需更新
四、最終執行 finishClassComponent, 進行錯誤判斷的處理和判斷是否能夠跳過更新的過程,從新調和子節點 reconcileChildren
function updateClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps, renderExpirationTime: ExpirationTime, ) { // ...... const instance = workInProgress.stateNode; let shouldUpdate; if (instance === null) { if (current !== null) { // An class component without an instance only mounts if it suspended // inside a non- concurrent tree, in an inconsistent state. We want to // tree it like a new mount, even though an empty version of it already // committed. Disconnect the alternate pointers. current.alternate = null; workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect workInProgress.effectTag |= Placement; } // In the initial pass we might need to construct the instance. // 若是還沒建立實例,初始化 // 執行構造函數,獲得實例instance // workInProgress.stateNode = instance constructClassInstance( workInProgress, Component, nextProps, renderExpirationTime, ); // 掛載 // 主要工做是更新instance.state,而且執行一些生命週期 mountClassInstance( workInProgress, Component, nextProps, renderExpirationTime, ); shouldUpdate = true; } else if (current === null) { // In a resume, we'll already have an instance we can reuse. // 渲染中斷 shouldUpdate = resumeMountClassInstance( workInProgress, Component, nextProps, renderExpirationTime, ); } else { shouldUpdate = updateClassInstance( current, workInProgress, Component, nextProps, renderExpirationTime, ); } // 完成 class 組件更新 return finishClassComponent( current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime, ); }
一、建立一個class組件實例(instance),即業務中寫好的class component。
二、將實例賦值給stateNode屬性:workInProgress.stateNode = instance
function constructClassInstance( workInProgress: Fiber, ctor: any, props: any, renderExpirationTime: ExpirationTime, ): any { // ...... // 建立實例,這裏生成 class 組件實例 const instance = new ctor(props, context); // memoizedState 爲實例的 state, 沒有就爲 null const state = (workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null); adoptClassInstance(workInProgress, instance); // ...... // Cache unmasked context so we can avoid recreating masked context unless necessary. // ReactFiberContext usually updates this cache but can't for newly-created instances. if (isLegacyContextConsumer) { cacheContext(workInProgress, unmaskedContext, context); } return instance; }
adoptClassInstance:
function adoptClassInstance(workInProgress: Fiber, instance: any): void { // 爲 instance.updater 賦值 classComponentUpdater, 用於組件經過 ReactDOM.render 或 setState 進行更新 // 給 class 組件實例的 updater 設置 instance.updater = classComponentUpdater; // 將實例賦值給stateNode屬性 workInProgress.stateNode = instance; // The instance needs access to the fiber so that it can schedule updates // 給 instance._reactInternalFiber 賦值當前的 fiber 對象 ReactInstanceMap.set(instance, workInProgress); // ...... }
這裏有咱們熟悉的componentWillMount生命週期出現啦,不過新版React已經移除了,額,我不說了句廢話麼…… 哈哈並非,我只是讓你們更加了解這個更新過程。
一、從updateQueue裏面獲取到全部的要更新的state,調用processUpdateQueue函數遍歷updateQueue,遍歷的過程會判斷每一個update的優先級,決定是否要跳過這個更新。
二、若是這個update須要更新,調用getStateFromUpdate獲取到新的state。
三、更新成最新的state:instance.state = workInProgress.memoizedState;
四、調用React新的生命週期函數:getDerivedStateFromProps而且執行,這個生命週期可能改變State,因此再次須要instance.state = workInProgress.memoizedState
五、若是沒有使用getDerivedStateFromProps而使用componentWillMount,這裏爲了兼容舊版。執行componentWillMount,這個生命週期可能改變State。
六、最後標記 componentDidMount 生命週期,待到提交階段更新完 dom 後執行
// Invokes the mount life-cycles on a previously never rendered instance. function mountClassInstance( workInProgress: Fiber, ctor: any, newProps: any, renderExpirationTime: ExpirationTime, ): void { const instance = workInProgress.stateNode; instance.props = newProps; instance.state = workInProgress.memoizedState; instance.refs = emptyRefsObject; // ...... // 計算更新 state 獲得新的state let updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, newProps, instance, renderExpirationTime, ); // 更新成最新的state instance.state = workInProgress.memoizedState; } // 判斷是否有getDerivedStateFromProps生命週期而且執行,這個生命週期可能改變State const getDerivedStateFromProps = ctor.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === 'function') { applyDerivedStateFromProps( workInProgress, ctor, getDerivedStateFromProps, newProps, ); // 更新成最新的state instance.state = workInProgress.memoizedState; } // In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. // 判斷是否有componentWillMount生命週期而且執行,這個生命週期也可能改變State if ( typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function') ) { callComponentWillMount(workInProgress, instance); // If we had additional state updates during this life-cycle, let's // process them now. updateQueue = workInProgress.updateQueue; // 若是改變了state,就有新的update加入updateQueue了 if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, newProps, instance, renderExpirationTime, ); // 更新成最新的state instance.state = workInProgress.memoizedState; } } // 最後標記 componentDidMount 生命週期,待到提交階段更新完 dom 後執行 if (typeof instance.componentDidMount === 'function') { workInProgress.effectTag |= Update; } }
一、執行getDerivedStateFromProps
二、像上面的方法同樣,調用processUpdateQueue獲得更新後的State。
三、由組件的 shouldComponentUpdate 判斷是否要更新(shouldUpdate ),pureComponent 會自動比較 props。
四、函數返回shouldUpdate
function resumeMountClassInstance( workInProgress: Fiber, ctor: any, newProps: any, renderExpirationTime: ExpirationTime, ): boolean { const instance = workInProgress.stateNode; const oldProps = workInProgress.memoizedProps; instance.props = oldProps; // ...... const getDerivedStateFromProps = ctor.getDerivedStateFromProps; const hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what // ever the previously attempted to render - not the "current". However, // during componentDidUpdate we pass the "current" props. // In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. if ( !hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function') ) { if (oldProps !== newProps || oldContext !== nextContext) { // 執行getDerivedStateFromProps生命週期 callComponentWillReceiveProps( workInProgress, instance, newProps, nextContext, ); } } resetHasForceUpdateBeforeProcessing(); // 調用processUpdateQueue獲得更新後的State const oldState = workInProgress.memoizedState; let newState = (instance.state = oldState); let updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, newProps, instance, renderExpirationTime, ); newState = workInProgress.memoizedState; } if ( oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing() ) { // If an update was already in progress, we should schedule an Update // effect even though we're bailing out, so that cWU/cDU are called. if (typeof instance.componentDidMount === 'function') { workInProgress.effectTag |= Update; } return false; } if (typeof getDerivedStateFromProps === 'function') { applyDerivedStateFromProps( workInProgress, ctor, getDerivedStateFromProps, newProps, ); newState = workInProgress.memoizedState; } // 由組件的 shouldComponentUpdate 判斷是否要更新(shouldUpdate ),pureComponent 會自動比較 props。 const shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext, ); if (shouldUpdate) { // In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. // 判斷執行那些生命週期 if ( !hasNewLifecycles && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function') ) { startPhaseTimer(workInProgress, 'componentWillMount'); if (typeof instance.componentWillMount === 'function') { instance.componentWillMount(); } if (typeof instance.UNSAFE_componentWillMount === 'function') { instance.UNSAFE_componentWillMount(); } stopPhaseTimer(); } // 標記componentDidMount,中斷的組件仍然按照首次掛載執行 if (typeof instance.componentDidMount === 'function') { workInProgress.effectTag |= Update; } } else { // If an update was already in progress, we should schedule an Update // effect even though we're bailing out, so that cWU/cDU are called. if (typeof instance.componentDidMount === 'function') { workInProgress.effectTag |= Update; } // If shouldComponentUpdate returned false, we should still update the // memoized state to indicate that this work can be reused. workInProgress.memoizedProps = newProps; workInProgress.memoizedState = newState; } // Update the existing instance's state, props, and context pointers even // if shouldComponentUpdate returns false. // 更新props和props即便shouldComponentUpdate returns false instance.props = newProps; instance.props = newState; instance.context = nextContext; return shouldUpdate; }
過程與 resumeMountClassInstance 類似, 不過執行的是 willUpdate, 標記 didUpdate, getSnapshotBeforeUpdate
function updateClassInstance( current: Fiber, workInProgress: Fiber, ctor: any, newProps: any, renderExpirationTime: ExpirationTime, ): boolean { const instance = workInProgress.stateNode; const oldProps = workInProgress.memoizedProps; instance.props = oldProps; // ...... const getDerivedStateFromProps = ctor.getDerivedStateFromProps; const hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what // ever the previously attempted to render - not the "current". However, // during componentDidUpdate we pass the "current" props. // In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. if ( !hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function') ) { if (oldProps !== newProps || oldContext !== nextContext) { callComponentWillReceiveProps( workInProgress, instance, newProps, nextContext, ); } } resetHasForceUpdateBeforeProcessing(); const oldState = workInProgress.memoizedState; let newState = (instance.state = oldState); let updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, newProps, instance, renderExpirationTime, ); newState = workInProgress.memoizedState; } if ( oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing() ) { // If an update was already in progress, we should schedule an Update // effect even though we're bailing out, so that cWU/cDU are called. if (typeof instance.componentDidUpdate === 'function') { if ( oldProps !== current.memoizedProps || oldState !== current.memoizedState ) { workInProgress.effectTag |= Update; } } if (typeof instance.getSnapshotBeforeUpdate === 'function') { if ( oldProps !== current.memoizedProps || oldState !== current.memoizedState ) { workInProgress.effectTag |= Snapshot; } } return false; } if (typeof getDerivedStateFromProps === 'function') { applyDerivedStateFromProps( workInProgress, ctor, getDerivedStateFromProps, newProps, ); newState = workInProgress.memoizedState; } const shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext, ); if (shouldUpdate) { // In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. if ( !hasNewLifecycles && (typeof instance.UNSAFE_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function') ) { startPhaseTimer(workInProgress, 'componentWillUpdate'); if (typeof instance.componentWillUpdate === 'function') { instance.componentWillUpdate(newProps, newState, nextContext); } if (typeof instance.UNSAFE_componentWillUpdate === 'function') { instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext); } stopPhaseTimer(); } if (typeof instance.componentDidUpdate === 'function') { workInProgress.effectTag |= Update; } if (typeof instance.getSnapshotBeforeUpdate === 'function') { workInProgress.effectTag |= Snapshot; } } else { // If an update was already in progress, we should schedule an Update // effect even though we're bailing out, so that cWU/cDU are called. if (typeof instance.componentDidUpdate === 'function') { if ( oldProps !== current.memoizedProps || oldState !== current.memoizedState ) { workInProgress.effectTag |= Update; } } if (typeof instance.getSnapshotBeforeUpdate === 'function') { if ( oldProps !== current.memoizedProps || oldState !== current.memoizedState ) { workInProgress.effectTag |= Snapshot; } } // If shouldComponentUpdate returned false, we should still update the // memoized props/state to indicate that this work can be reused. workInProgress.memoizedProps = newProps; workInProgress.memoizedState = newState; } // Update the existing instance's state, props, and context pointers even // if shouldComponentUpdate returns false. instance.props = newProps; instance.state = newState; instance.context = nextContext; return shouldUpdate; }
一、沒更新也沒錯誤捕獲直接跳過,不會進行從新渲染
二、有錯誤捕獲,class 組件沒有 getDerivedStateFromError, nextChildren = null
三、有錯誤捕獲,class 組件有 getDerivedStateFromError ,直接執行 instance.render() 得到最新的 nextChildren, getDerivedStateFromError 在函數外 catch 到錯誤而且執行當即更新爲正確的 state, 因此能夠執行 instance.render()
四、沒捕獲錯誤 執行nextChildren = instance.render();
五、有錯誤強行計算child進行調和,調用forceUnmountCurrentAndReconcile
六、正常狀況直接調和子節點,調用reconcileChildren
function finishClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean, renderExpirationTime: ExpirationTime, ) { // ...... // 沒更新也沒錯誤捕獲直接跳過 if (!shouldUpdate && !didCaptureError) { // Context providers should defer to sCU for rendering if (hasContext) { invalidateContextProvider(workInProgress, Component, false); } return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } const instance = workInProgress.stateNode; // Rerender ReactCurrentOwner.current = workInProgress; let nextChildren; // 有錯誤捕獲 if ( didCaptureError && typeof Component.getDerivedStateFromError !== 'function' ) { // If we captured an error, but getDerivedStateFrom catch is not defined, // unmount all the children. componentDidCatch will schedule an update to // re-render a fallback. This is temporary until we migrate everyone to // the new API. // TODO: Warn in a future release. // class 組件沒有 getDerivedStateFromError, nextChildren = null nextChildren = null; if (enableProfilerTimer) { stopProfilerTimerIfRunning(workInProgress); } } else { // ...... //class 組件有 getDerivedStateFromError ,直接執行 instance.render() 得到最新的 nextChildren, getDerivedStateFromError 在函數外 catch 到錯誤而且執行當即更新爲正確的 state, 因此能夠執行 instance.render() //沒捕獲錯誤 執行 instance.render() nextChildren = instance.render(); } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; if (current !== null && didCaptureError) { // If we're recovering from an error, reconcile without reusing any of // the existing children. Conceptually, the normal children and the children // that are shown on error are two different sets, so we shouldn't reuse // normal children even if their identities match. // 有錯誤強行計算child進行調和,調用forceUnmountCurrentAndReconcile forceUnmountCurrentAndReconcile( current, workInProgress, nextChildren, renderExpirationTime, ); } else { // 正常狀況直接調和子節點,調用reconcileChildren reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); } // Memoize state using the values we just used to render. // TODO: Restructure so we never read values from the instance. workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it. if (hasContext) { invalidateContextProvider(workInProgress, Component, true); } return workInProgress.child; }
一、咱們能夠回到createFiberFromTypeAndProps函數查看,fiber 剛建立的時候 fiberTag 都爲 IndeterminateComponent 類型,只有當 class 組件有 construct 才爲 class 組件類型
二、因此這個函數中作如下判斷:
符合 class 組件條件按 class 組件更新
不然就按函數組件類型更新
注:只存在於首次更新的時候,只有首次更新的時候不肯定 fiberTag 類型
function mountIndeterminateComponent( _current, workInProgress, Component, renderExpirationTime, ) { if (_current !== null) { // An indeterminate component only mounts if it suspended inside a non- // concurrent tree, in an inconsistent state. We want to tree it like // a new mount, even though an empty version of it already committed. // Disconnect the alternate pointers. _current.alternate = null; workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect workInProgress.effectTag |= Placement; } const props = workInProgress.pendingProps; const unmaskedContext = getUnmaskedContext(workInProgress, Component, false); const context = getMaskedContext(workInProgress, unmaskedContext); prepareToReadContext(workInProgress, renderExpirationTime); let value; // ...... value = Component(props, context); // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; if ( typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined ) { // 按 class 組件更新 // Proceed under the assumption that this is a class instance workInProgress.tag = ClassComponent; // Push context providers early to prevent context stack mismatches. // During mounting we don't know the child context yet as the instance doesn't exist. // We will invalidate the child context in finishClassComponent() right after rendering. let hasContext = false; if (isLegacyContextProvider(Component)) { hasContext = true; pushLegacyContextProvider(workInProgress); } else { hasContext = false; } workInProgress.memoizedState = value.state !== null && value.state !== undefined ? value.state : null; const getDerivedStateFromProps = Component.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === 'function') { applyDerivedStateFromProps( workInProgress, Component, getDerivedStateFromProps, props, ); } adoptClassInstance(workInProgress, value); mountClassInstance(workInProgress, Component, props, renderExpirationTime); return finishClassComponent( null, workInProgress, Component, true, hasContext, renderExpirationTime, ); } else { // 按 函數 組件更新 // Proceed under the assumption that this is a function component workInProgress.tag = FunctionComponent; // ...... reconcileChildren(null, workInProgress, value, renderExpirationTime); return workInProgress.child; } }
這種狀況只會出如今ReactDOM.render渲染的時候
一、調用processUpdateQueue獲得新的state
二、nextChildren = nextState.element
三、第一次渲染mountChildFibers
四、後續渲染reconcileChildren
function updateHostRoot(current, workInProgress, renderExpirationTime) { pushHostRootContext(workInProgress); const updateQueue = workInProgress.updateQueue; invariant( updateQueue !== null, 'If the root does not have an updateQueue, we should have already ' + 'bailed out. This error is likely caused by a bug in React. Please ' + 'file an issue.', ); 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; // Caution: React DevTools currently depends on this property // being called "element". const nextChildren = nextState.element; if (nextChildren === prevChildren) { // If the state is the same as before, that's a bailout because we had // no work that expires at this time. resetHydrationState(); return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } const root: FiberRoot = workInProgress.stateNode; if ( (current === null || current.child === null) && root.hydrate && enterHydrationState(workInProgress) ) { // If we don't have any current children this might be the first pass. // We always try to hydrate. If this isn't a hydration pass there won't // be any children to hydrate which is effectively the same thing as // not hydrating. // This is a bit of a hack. We track the host root as a placement to // know that we're currently in a mounting state. That way isMounted // works as expected. We must reset this before committing. // TODO: Delete this when we delete isMounted and findDOMNode. workInProgress.effectTag |= Placement; // Ensure that children mount into this root without tracking // side-effects. This ensures that we don't store Placement effects on // nodes that will be hydrated. workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderExpirationTime, ); } else { // Otherwise reset hydration state in case we aborted and resumed another // root. reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); resetHydrationState(); } return workInProgress.child; }
一、dom 標籤內是純文本 nextChildren 爲 null,直接渲染文本內容
二、判斷 concurrentMode 異步組件是否有 hidden 屬性,異步組件 hidden 永不更新
三、最後進行 reconcileChildren
function updateHostComponent(current, workInProgress, renderExpirationTime) { pushHostContext(workInProgress); if (current === null) { tryToClaimNextHydratableInstance(workInProgress); } 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) { // We special case a direct text child of a host node. This is a common // case. We won't handle it as a reified child. We will instead handle // this in the host environment that also have access to this prop. That // avoids allocating another HostText fiber and traversing it. //dom 標籤內是純文本 nextChildren 爲 null,直接渲染文本內容 nextChildren = null; } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) { // If we're switching from a direct text child to a normal child, or to // empty, we need to schedule the text content to be reset. workInProgress.effectTag |= ContentReset; } markRef(current, workInProgress); // Check the host config to see if the children are offscreen/hidden. if ( renderExpirationTime !== Never && workInProgress.mode & ConcurrentMode && shouldDeprioritizeSubtree(type, nextProps) ) { // Schedule this fiber to re-render at offscreen priority. Then bailout. //判斷 concurrentMode 異步組件是否有 hidden 屬性,異步組件 hidden 永不更新 workInProgress.expirationTime = Never; return null; } // 最後進行 reconcileChildren reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); return workInProgress.child; }
文本內容不須要構建 fiber 結構,直接在提交階段更新就好了,因此直接return null
function updateHostText(current, workInProgress) { if (current === null) { tryToClaimNextHydratableInstance(workInProgress); } // Nothing to do here. This is terminal. We'll do the completion step // immediately after. // 文本內容不須要構建 fiber 結構,直接在提交階段更新就好了,因此直接return null return null; }
文章若有不妥,歡迎指正~