React16源碼解析(六)-更新流程渲染階段2

React源碼解析系列文章歡迎閱讀:
React16源碼解析(一)- 圖解Fiber架構
React16源碼解析(二)-創建更新
React16源碼解析(三)-ExpirationTime
React16源碼解析(四)-Scheduler
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
React16源碼解析(八)-更新流程提交階段
正在更新中...

接着上篇文章,在beginWork中,通過workInProgress.tag判斷當前是什麼類型的節點而調用不同的更新函數。這篇文章講解各種類型的組件的更新過程。

updateFunctionComponent

在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,
    );
}

1、調用函數,即業務中寫好的函數組件。得到一個ReactElement,即nextChildren
2、調用reconcileChildren,第一個參數current=當前fiber節點。第二個參數workInProgress=需要更新的fiber節點。第三個參數nextChildren,上面函數的返回值。
3、上面調用的reconcileChildren方法,其實是改變了workInProgress.child。
4、返回workInProgress.child。

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;
}

reconcileChildren

1、判斷當前節點是否爲null,如果是第一次渲染,current=null,則調用mountChildFibers,函數返回值賦值給workInProgress.child。
2、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);

ChildReconciler

1、你會發現,這個一個無敵長的巨型方法。
2、看到這個方法的最後return reconcileChildFibers;
3、往上面找到這個方法reconcileChildFibers,就在return 的上面

function ChildReconciler(shouldTrackSideEffects) {
    // ......
    function reconcileChildFibers(......){
        // ......
    }
    return reconcileChildFibers;
}

reconcileChildFibers

1、這個方法第三個參數newChild即爲我們調用函數組件返回的新的child。
2、判斷newChild是什麼類型的節點,不同類型對應不同的操作。比如newChild.$$typeof=REACT_ELEMENT_TYPE,則return placeSingleChild(reconcileSingleElement())。如果是數組,調用reconcileChildrenArray()進行調和,還有可能是REACT_PORTAL_TYPE、string、number、Iterator等。
3、如果這個newChild上面的都不符合,但又是個對象但又不是null,那麼就是一個非法的定義了。就throwOnInvalidObjectType拋出錯誤。
4、最後,調用deleteRemainingChildren刪除掉所有子節點。因爲到了最後,只有可能newChild === null,說明新的更新清空掉了所有子節點。

注:deleteRemainingChildren 這個函數裏面調用deleteChild逐個刪除,但刪除子節點並不是真的刪除這個對象,而是通過 firstEffect、lastEffect、nextEffect 屬性來維護一個 EffectList(鏈表結構),通過 effectTag 標記當前刪除操作,這些信息都會在 commit 階段使用到

 // 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算法:
    1、單個元素,調用reconcileSingleElement
    2、單個Portal元素,調用reconcileSinglePortal
    3、string或者number,調用reconcileSingleTextNode
    4、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;
  }

reconcileSingleElement

調和單個子節點。
1、通過key判斷是否節點是否可以複用
2、根據節點的不同創建不同的fiber對象,如果是REACT_FRAGMENT_TYPE類型通過createFiberFromFragment創建fiber對象,其他類型通過createFiberFromElement創建fiber對象。
3、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;
  }

reconcileChildrenArray

1、用一個循環相同位置進行比較,找到第一個不可複用的節點爲止,其中updateSlot函數用來判斷新老節點是否可以複用
2、新節點已經遍歷完畢,直接把剩下的老節點刪除了就行了
3、老節點已經遍歷完畢,根據剩餘新的節點直接創建 Fiber 
4、移動的情況下進行節點複用:
把所有老數組元素按 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;
    /**
    1、用一個循環相同位置進行比較,找到第一個不可複用的節點爲止,其中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;
    }

    // 2、新節點已經遍歷完畢,直接把剩下的老節點刪除了就行了
    if (newIdx === newChildren.length) {
      // We've reached the end of the new children. We can delete the rest.
      deleteRemainingChildren(returnFiber, oldFiber);
      return resultingFirstChild;
    }

    // 3、老節點已經遍歷完畢,根據剩餘新的節點直接創建 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;
  }

updateClassComponent

在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,
    );
}

這個函數的作用是對未初始化的類組件進行初始化,對已經初始化的組件更新重用。

1、如果還沒創建實例,初始化,說明是第一次渲染(instance === null)
調用constructClassInstance,執行構造函數,生成實例instance
然後在調用mountClassInstance,掛載實例,主要工作是更新instance.state,並且執行一些生命週期
2、渲染被中斷 instance !== null, current === null
調用resumeMountClassInstance 複用實例但還是調用首次渲染的生命週期,這個函數如果反覆false則組件無需更新
3、更新渲染 instance !== null, current !== null
調用updateClassInstance,調用 didUpdate 和 componentWillReceiveProp 生命週期,這個函數如果反覆false則組件無需更新
4、最終執行 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,
  );
}

constructClassInstance

1、創建一個class組件實例(instance),即業務中寫好的class component。
2、將實例賦值給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);
  // ......
}

mountClassInstance

這裏有我們熟悉的componentWillMount生命週期出現啦,不過新版React已經移除了,額,我不說了句廢話麼…… 哈哈並不是,我只是讓大家更加了解這個更新過程。

1、從updateQueue裏面獲取到所有的要更新的state,調用processUpdateQueue函數遍歷updateQueue,遍歷的過程會判斷每個update的優先級,決定是否要跳過這個更新。
2、如果這個update需要更新,調用getStateFromUpdate獲取到新的state。
3、更新成最新的state:instance.state = workInProgress.memoizedState;
4、調用React新的生命週期函數:getDerivedStateFromProps並且執行,這個生命週期可能改變State,所以再次需要instance.state = workInProgress.memoizedState
5、如果沒有使用getDerivedStateFromProps而使用componentWillMount,這裏爲了兼容舊版。執行componentWillMount,這個生命週期可能改變State。
6、最後標記 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;
  }
}

resumeMountClassInstance

1、執行getDerivedStateFromProps
2、像上面的方法一樣,調用processUpdateQueue得到更新後的State。
3、由組件的 shouldComponentUpdate 判斷是否要更新(shouldUpdate ),pureComponent 會自動比較 props。
4、函數返回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;
}

updateClassInstance

過程與 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;
}

finishClassComponent

1、沒更新也沒錯誤捕獲直接跳過,不會進行重新渲染
2、有錯誤捕獲,class 組件沒有 getDerivedStateFromError, nextChildren = null
3、有錯誤捕獲,class 組件有 getDerivedStateFromError ,直接執行 instance.render() 獲得最新的 nextChildren, getDerivedStateFromError 在函數外 catch 到錯誤並且執行立即更新爲正確的 state, 所以可以執行 instance.render()
4、沒捕獲錯誤 執行nextChildren = instance.render();
5、有錯誤強行計算child進行調和,調用forceUnmountCurrentAndReconcile
6、正常情況直接調和子節點,調用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;
}

mountIndeterminateComponent

1、我們可以回到createFiberFromTypeAndProps函數查看,fiber 剛創建的時候 fiberTag 都爲 IndeterminateComponent 類型,只有當 class 組件有 construct 才爲 class 組件類型
2、所以這個函數中做以下判斷:
符合 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;
  }
}

updateHostRoot

這種情況只會出現在ReactDOM.render渲染的時候
1、調用processUpdateQueue得到新的state
2、nextChildren = nextState.element
3、第一次渲染mountChildFibers
4、後續渲染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;
}

updateHostComponent

1、dom 標籤內是純文本 nextChildren 爲 null,直接渲染文本內容
2、判斷 concurrentMode 異步組件是否有 hidden 屬性,異步組件 hidden 永不更新
3、最後進行 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;
}

updateHostText

文本內容不需要構建 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;
}

任世界紛繁複雜,仍舊保持可愛。
我是小柚子小仙女。文章如有不妥,歡迎指正~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章