React源碼解析之workLoop

前言:
React源碼解析之renderRoot概覽中,提到了renderRoot()會調用workLoop()/workLoopSync()進行循環單元的更新:javascript

function renderRoot(){
  xxx
  xxx
  if (workInProgress !== null) {
      //執行每一個節點的更新
      if (isSync) {
        workLoopSync();
      } else {
        //判斷是否須要繼續調用performUnitOfWork
        workLoop();
      }
   }
  xxx
  xxx
}
複製代碼

本文就來說解workLoop及其內部的方法邏輯php

注意:
本文涉及到 fiber 對象的部分屬性,請參考:React源碼解析之RootFiber java

1、workLoop
做用:
循環執行performUnitOfWork並賦值給workInProgress,直到workInProgress值爲空,則停止循環react

源碼:git

//同步的 workLoop,說明是不能夠被中斷的
function workLoopSync({
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

//異步的 workLoop,說明是能夠被中斷的
//判斷是否須要繼續調用performUnitOfWork
function workLoop({
  // Perform work until Scheduler asks us to yield
  /*nextUnitOfWork =》workInProgress*/
  //未到達根節點時

  //有workInProgress.child的時候,一直循環,直到全部節點更新完畢
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}
複製代碼

解析:
workInProgress 是一個 fiber 對象,它值不爲 null 意味着該 fiber 對象上仍然有更新要執行github

2、performUnitOfWork
做用:
調用beginWork,從父至子,進行組件(節點)更新;
調用completeUnitOfWork,從子至父,根據 effectTag,對節點進行一些處理web

源碼:app

//從上至下遍歷、操做節點,至底層後,再從下至上,根據effectTag 操做節點
//unitOfWork 即 workInProgress,是一個 fiber 對象
function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  //current <=> workInProgress
  //獲取當前節點
  const current = unitOfWork.alternate;
  //在unitOfWork上作個標記,不看
  startWorkTimer(unitOfWork);
  //dev 環境,不看
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    //進行節點操做,並建立子節點
    //current: workInProgress.alternate
    //unitOfWork: workInProgress

    //workInProgress.child
    //判斷fiber有無更新,有更新則進行相應的組件更新,無更新則複製節點
    next = beginWork(current, unitOfWork, renderExpirationTime);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, renderExpirationTime);
  }
  //不看
  resetCurrentDebugFiberInDEV();
  //將待更新的 props 替換成正在用的 props
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  //說明已經更新到了最底層的葉子節點,而且葉子節點的兄弟節點也已經遍歷完
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    //當從上到下遍歷完成後,completeUnitOfWork 會從下到上根據effact tag進行一些處理
    next = completeUnitOfWork(unitOfWork);
  }

  ReactCurrentOwner.current = null;
  return next;
}
複製代碼

解析:
(1) 獲取當前節點current
(2) 執行beginWork(current, unitOfWork, renderExpirationTime)進行組件的更新,返回的值賦給next
(3) 將unitOfWork上待更新的 props 替換成正在用的 props
(4) 若是next爲 null,則執行completeUnitOfWork,從下到上遍歷,並根據 effectTag 進行一些處理
(5) 最終返回next(next 爲 fiber 對象)dom

看下beginWork()completeUnitOfWork()會在後面文章中解析。異步

3、beginWork
注意:
switch…case… 那超長的兩段直接跳過

做用:
判斷fiber有無更新,有更新則進行相應的組件更新,無更新則複製節點

源碼:

//判斷fiber有無更新,有更新則進行相應的組件更新,無更新則複製節點
//current: workInProgress.alternate
function beginWork(
  current: Fiber | null,
  //workInProgress建立的子節點也是workInProgress
  workInProgress: Fiber,
  //標記 該次渲染中,優先級最高的點
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  //只有當調用 react.domRender的時候,rootFiber的expirationTime纔有值,rootFiber 纔會更新

  //獲取 fiber 對象上更新的過時時間
  const updateExpirationTime = workInProgress.expirationTime;


  //判斷是否是第一次渲染
  //若是不是第一次渲染
  if (current !== null) {
    //上一次渲染完成後的props,即 oldProps
    const oldProps = current.memoizedProps;
    //新的變更帶來的props,即newProps
    const newProps = workInProgress.pendingProps;

    if (
      //先後 props 是否不相等
      oldProps !== newProps ||
      //是否有老版本的 context 使用,而且發生了變化
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      //開發環境永遠是 false
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      //判斷接收到了更新 update
      didReceiveUpdate = true;
    }
    //有更新,可是優先級不高,在本次渲染過程當中不須要執行,設爲 false
    else if (updateExpirationTime < renderExpirationTime) {
      didReceiveUpdate = false;
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      //根據workInProgress的tag,進行相應組件的更新
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          resetHydrationState();
          break;
        case HostComponent:
          pushHostContext(workInProgress);
          if (
            workInProgress.mode & ConcurrentMode &&
            renderExpirationTime !== Never &&
            shouldDeprioritizeSubtree(workInProgress.type, newProps)
          ) {
            if (enableSchedulerTracing) {
              markSpawnedWork(Never);
            }
            // Schedule this fiber to re-render at offscreen priority. Then bailout.
            workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
            return null;
          }
          break;
        case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            pushLegacyContextProvider(workInProgress);
          }
          break;
        }
        case HostPortal:
          pushHostContainer(
            workInProgress,
            workInProgress.stateNode.containerInfo,
          );
          break;
        case ContextProvider: {
          const newValue = workInProgress.memoizedProps.value;
          pushProvider(workInProgress, newValue);
          break;
        }
        case Profiler:
          if (enableProfilerTimer) {
            workInProgress.effectTag |= Update;
          }
          break;
        case SuspenseComponent: {
          const state: SuspenseState | null = workInProgress.memoizedState;
          const didTimeout = state !== null;
          if (didTimeout) {
            // If this boundary is currently timed out, we need to decide
            // whether to retry the primary children, or to skip over it and
            // go straight to the fallback. Check the priority of the primary
            // child fragment.
            const primaryChildFragment: Fiber = (workInProgress.child: any);
            const primaryChildExpirationTime =
              primaryChildFragment.childExpirationTime;
            if (
              primaryChildExpirationTime !== NoWork &&
              primaryChildExpirationTime >= renderExpirationTime
            ) {
              // The primary children have pending work. Use the normal path
              // to attempt to render the primary children again.
              return updateSuspenseComponent(
                current,
                workInProgress,
                renderExpirationTime,
              );
            } else {
              pushSuspenseContext(
                workInProgress,
                setDefaultShallowSuspenseContext(suspenseStackCursor.current),
              );
              // The primary children do not have pending work with sufficient
              // priority. Bailout.
              const child = bailoutOnAlreadyFinishedWork(
                current,
                workInProgress,
                renderExpirationTime,
              );
              if (child !== null) {
                // The fallback children have pending work. Skip over the
                // primary children and work on the fallback.
                return child.sibling;
              } else {
                return null;
              }
            }
          } else {
            pushSuspenseContext(
              workInProgress,
              setDefaultShallowSuspenseContext(suspenseStackCursor.current),
            );
          }
          break;
        }
        case DehydratedSuspenseComponent: {
          if (enableSuspenseServerRenderer) {
            pushSuspenseContext(
              workInProgress,
              setDefaultShallowSuspenseContext(suspenseStackCursor.current),
            );
            // We know that this component will suspend again because if it has
            // been unsuspended it has committed as a regular Suspense component.
            // If it needs to be retried, it should have work scheduled on it.
            workInProgress.effectTag |= DidCapture;
          }
          break;
        }
        case SuspenseListComponent: {
          const didSuspendBefore =
            (current.effectTag & DidCapture) !== NoEffect;

          const childExpirationTime = workInProgress.childExpirationTime;
          if (childExpirationTime < renderExpirationTime) {
            // If none of the children had any work, that means that none of
            // them got retried so they'll still be blocked in the same way
            // as before. We can fast bail out.
            pushSuspenseContext(workInProgress, suspenseStackCursor.current);
            if (didSuspendBefore) {
              workInProgress.effectTag |= DidCapture;
            }
            return null;
          }

          if (didSuspendBefore) {
            // If something was in fallback state last time, and we have all the
            // same children then we're still in progressive loading state.
            // Something might get unblocked by state updates or retries in the
            // tree which will affect the tail. So we need to use the normal
            // path to compute the correct tail.
            return updateSuspenseListComponent(
              current,
              workInProgress,
              renderExpirationTime,
            );
          }

          // If nothing suspended before and we're rendering the same children,
          // then the tail doesn't matter. Anything new that suspends will work
          // in the "together" mode, so we can continue from the state we had.
          let renderState = workInProgress.memoizedState;
          if (renderState !== null) {
            // Reset to the "together" mode in case we've started a different
            // update in the past but didn't complete it.
            renderState.rendering = null;
            renderState.tail = null;
          }
          pushSuspenseContext(workInProgress, suspenseStackCursor.current);
          break;
        }
        case EventComponent:
          if (enableFlareAPI) {
            pushHostContextForEventComponent(workInProgress);
          }
          break;
      }
      //跳過該節點及全部子節點的更新
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  } else {
    didReceiveUpdate = false;
  }

  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;
  //若是節點是有更新的
  //根據節點類型進行組件的更新
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderExpirationTime,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    //FunctionComponent的更新
    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,
      );
    }
    //ClassComponent的更新
    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,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostPortal:
      return updatePortalComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderExpirationTime);
    case Mode:
      return updateMode(current, workInProgress, renderExpirationTime);
    case Profiler:
      return updateProfiler(current, workInProgress, renderExpirationTime);
    case ContextProvider:
      return updateContextProvider(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ContextConsumer:
      return updateContextConsumer(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      // Resolve outer props first, then resolve inner props.
      let resolvedProps = resolveDefaultProps(type, unresolvedProps);
      if (__DEV__) {
        if (workInProgress.type !== workInProgress.elementType) {
          const outerPropTypes = type.propTypes;
          if (outerPropTypes) {
            checkPropTypes(
              outerPropTypes,
              resolvedProps, // Resolved for outer only
              'prop',
              getComponentName(type),
              getCurrentFiberStackInDev,
            );
          }
        }
      }
      resolvedProps = resolveDefaultProps(type.type, resolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case DehydratedSuspenseComponent: {
      if (enableSuspenseServerRenderer) {
        return updateDehydratedSuspenseComponent(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
      break;
    }
    case SuspenseListComponent: {
      return updateSuspenseListComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
    case EventComponent: {
      if (enableFlareAPI) {
        return updateEventComponent(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
      break;
    }
  }
  invariant(
    false,
    'Unknown unit of work tag. This error is likely caused by a bug in ' +
      'React. Please file an issue.',
  );
}
複製代碼

解析:
(1) fiber 對象只要有更新,就會計算出一個 expirationTime
(2) memoizedPropspendingPropsfiber對象的屬性,具體請看:React源碼解析之RootFiber
(3) switch...case那段太長了,可跳過,後面會講FunctionComponent狀況時,組件是如何更新的
(4) 注意updateExpirationTime < renderExpirationTime的狀況,它的意思是當前 fiber 的更新優先級不高,在本次渲染的過程當中不會執行它的更新,因此會執行bailoutOnAlreadyFinishedWork,來跳過該節點及全部子節點的更新,再也不往下執行組件的更新
(5) 最後根據workInProgress.tag的不一樣狀況,來進行組件的更新

4、bailoutOnAlreadyFinishedWork
做用:
跳過該節點及該節點上全部子節點的更新

源碼:

//跳過該節點及全部子節點的更新
function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
)
Fiber | null 
{
  //不看
  cancelWorkTimer(workInProgress);

  if (current !== null) {
    // Reuse previous dependencies
    workInProgress.dependencies = current.dependencies;
  }

  if (enableProfilerTimer) {
    // Don't update "base" render times for bailouts.
    stopProfilerTimerIfRunning(workInProgress);
  }

  // Check if the children have any pending work.
  //expirationTime 表示該節點是否有更新,若是該節點有更新,可能會影響子節點的更新
  //若是expirationTime和childExpirationTime都沒有,則子樹是不須要更新的

  //因爲子孫節點形成的更新
  const childExpirationTime = workInProgress.childExpirationTime;
  //若是子樹不須要更新,則返回 null

  //childExpirationTime的一個好處就是快捷地知道子樹有沒有更新,從而跳過沒有更新的子樹
  //若是childExpirationTime爲空,react 還須要遍歷子樹來判斷是否更新
  if (childExpirationTime < renderExpirationTime) {
    // The children don't have any work either. We can skip them.
    // TODO: Once we add back resuming, we should check if the children are
    // a work-in-progress set. If so, we need to transfer their effects.

    //跳過整個子樹的更新渲染,這是一個很是大的優化
    return null;
  }
  //調和子節點
  else {
    // This fiber doesn't have work, but its subtree does. Clone the child
    // fibers and continue.
    //該節點不須要更新,子節點也不須要更新,因此只要複製子節點過來便可
    cloneChildFibers(current, workInProgress);
    return workInProgress.child;
  }
}
複製代碼

解析:
一般判斷子節點的更新是要遍歷子樹來獲取信息的,但 React 很是聰明地在子節點產生更新的時候,設置上 childExpirationTime,並最終在父節點上設置一個優先級最高的 childExpirationTime,這樣的話,若是childExpirationTime優先級小於renderExpirationTime的話,則跳過子樹的遍歷及更新渲染。

這是一個很是大的優化。

5、completeUnitOfWork
completeUnitOfWorkbeginWork的結構是相似的,但我會放在後面的文章去講

6、workLoop流程圖

7、GitHub
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索