首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之workLoop

React源码解析之workLoop

作者头像
进击的小进进
发布2019-11-18 22:52:41
1.2K0
发布2019-11-18 22:52:41
举报

前言

React源码解析之renderRoot概览中,提到了renderRoot()会调用 workLoop()/workLoopSync() 进行循环单元的更新:

function renderRoot(){
  xxx
  xxx
  if (workInProgress !== null) {
      //执行每个节点的更新
      if (isSync) {
        workLoopSync();
      } else {
        //判断是否需要继续调用performUnitOfWork
        workLoop();
      }
   }
  xxx
  xxx
}

本文就来讲解workLoop及其内部的方法逻辑

注意: 对于 fiber 对象的部分属性讲解,请参考:React源码解析之RootFiber

workLoop

作用: 循环执行performUnitOfWork并赋值给workInProgress,直到workInProgress值为空,则中止循环

源码:

//同步的 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 对象上仍然有更新要执行

performUnitOfWork

作用: 调用beginWork,从父至子,进行组件(节点)更新;调用completeUnitOfWork,从子至父,根据 effectTag,对节点进行一些处理

源码:

//从上至下遍历、操作节点,至底层后,再从下至上,根据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 对象)

看下beginWork()completeUnitOfWork()会在后面文章中解析。

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的不同情况,来进行组件的更新

四、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的话,则跳过子树的遍历及更新渲染。

这是一个非常大的优化。

completeUnitOfWork

completeUnitOfWorkbeginWork的结构是类似的,但我会放在后面的文章去讲

workLoop流程图

GitHub

https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • workLoop
  • performUnitOfWork
  • beginWork
  • 四、bailoutOnAlreadyFinishedWork
  • completeUnitOfWork
  • workLoop流程图
  • GitHub
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档