关于javascript:ReactFiber节点的更新入口beginWork

React的更新工作次要是调用一个叫做workLoop的工作循环去构建workInProgress树,构建过程分为两个阶段:向下遍历和向上回溯,向下和向上的过程中会对路径的每个节点进行beginWork和completeWork。

本文行将提到的beginWork是解决节点更新的入口,它会根据fiber节点的类型去调用不同的处理函数。

React对每个节点进行beginWork操作,进入beginWork后,首先判断节点及其子树是否有更新,若有更新,则会在计算新状态和diff之后生成新的Fiber,而后在新的fiber上标记flags(effectTag),最初return它的子节点,以便持续针对子节点进行beginWork。若它没有子节点,则返回null,这样阐明这个节点是末端节点,能够进行向上回溯,进入completeWork阶段。

点击进入React源码调试仓库。

beginWork的工作流程如下图,图中简化了流程,只对App节点进行了beginWork解决,其余节点流程类似

职责

通过概述可知beginWork阶段的整体工作是去更新节点,并返回子树,但真正的beginWork函数只是节点更新的入口,不会间接进行更新操作。作为入口,它的职责很显著,拦挡无需更新的节点。同时,它还会将context信息入到栈中(beginWork入栈,completeWork出栈),临时先不关注。

function beginWork(
    current: Fiber | null,
    workInProgress: Fiber,
    renderLanes: Lanes
): Fiber | null {
  // 获取workInProgress.lanes,可通过判断它是否为空去判断该节点是否须要更新
  const updateLanes = workInProgress.lanes;

  // 根据current是否存在判断以后是首次挂载还是后续的更新
  // 如果是更新,先看优先级够不够,不够的话就能调用bailoutOnAlreadyFinishedWork
  // 复用fiber节点来跳出对以后这个节点的解决了。
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (
        oldProps !== newProps ||
        hasLegacyContextChanged()
    ) {
      didReceiveUpdate = true;
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      // 此时无需更新
      didReceiveUpdate = false;
      switch (workInProgress.tag) {
        case HostRoot:
          ...
        case HostComponent:
          ...
        case ClassComponent:
          ...
        case HostPortal:
          ...
      }

      // 拦挡无需更新的节点
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  } else {
    didReceiveUpdate = false;
  }

  // 代码走到这里阐明的确要去解决节点了,此时会依据不同fiber的类型
  // 去调用它们各自的处理函数

  // 先清空workInProgress节点上的lanes,因为更新过程中用不到,
  // 在解决完updateQueue之后会从新赋值
  workInProgress.lanes = NoLanes;

  // 根据不同的节点类型来解决节点的更新
  switch (workInProgress.tag) {
    case IndeterminateComponent:
      ...
    case LazyComponent:
      ...
    case FunctionComponent:
      ...
      return updateFunctionComponent(
          current,
          workInProgress,
          Component,
          resolvedProps,
          renderLanes,
      );
    }
    case ClassComponent:
      ...
      return updateClassComponent(
          current,
          workInProgress,
          Component,
          resolvedProps,
          renderLanes,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);

    ......
  }
}

能够看出,一旦节点进入beginWork,会先去辨认该节点是否须要解决,若无需解决,则调用bailoutOnAlreadyFinishedWork复用节点,否则才真正去更新。

如何辨别更新与初始化过程

判断current是否存在。

这首先要了解current是什么,基于双缓冲的规定,调度更新时有两棵树,展现在屏幕上的current Tree和正在后盾基于current树构建的
workInProgress Tree。那么,current和workInProgress能够了解为镜像的关系。workLoop循环以后遍历到的workInProgress节点来自于它对应的current节点父级fiber的子节点(即current节点),所以workInProgress节点和current节点也是镜像的关系。

如果是首次渲染,对具体的workInProgress节点来说,它是没有current节点的,如果是在更新过程,因为current节点曾经在首次渲染时产生了,所以workInProgress节点有对应的current节点存在。

最终会依据节点是首次渲染还是更新来决定是创立fiber还是diff fiber。只不过更新时,如果节点的优先级不够会间接复用已有节点,即走跳出(bailout)的逻辑,而不是去走上面的更新逻辑。

复用节点过程

节点可复用示意它无需更新。在下面beginWork的代码中能够看到,若节点的优先级不满足要求,阐明它不必更新,会调用bailoutOnAlreadyFinishedWork函数,去复用current节点作为新的workInProgress树的节点。

beginWork函数中拦挡无需更新节点的逻辑

if (!includesSomeLane(renderLanes, updateLanes)) {
  ...

  // 此时无需更新,拦挡无需更新的节点
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

beginWork它的返回值有两种状况:

  • 返回以后节点的子节点,而后会以该子节点作为下一个工作单元持续beginWork,一直往下生成fiber节点,构建workInProgress树。
  • 返回null,以后fiber子树的遍历就此终止,从以后fiber节点开始往回进行completeWork。

bailoutOnAlreadyFinishedWork函数的返回值也是如此。

  • 返回以后节点的子节点,前置条件是以后节点的子节点有更新,此时以后节点未经解决,是能够间接复用的,复用的过程就是复制一份current节点的子节点,并把它return进来。
  • 返回null,前提是以后子节点没有更新,以后子树的遍历过程就此终止。开始completeWork。

从这个函数中,咱们也能够意识到,辨认以后fiber节点的子树有无更新显得尤为重要,这能够决定是否终止以后Fiber子树的遍历,将复杂度间接升高。实际上能够通过fiber.childLanes去辨认,childLanes如果不为空,表明子树中有须要更新的节点,那么须要持续往下走。

标记fiber.childLanes的过程是在开始调度时产生的,在markUpdateLaneFromFiberToRoot 函数中

带着上边的认知,来看一下源码理解具体的复用过程:

function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
): Fiber | null {

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

  // 标记有跳过的更新
  markSkippedUpdateLanes(workInProgress.lanes);

  // 如果子节点没有更新,返回null,终止遍历
  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    return null;
  } else {
    // 子节点有更新,那么从current上复制子节点,并return进来
    cloneChildFibers(current, workInProgress);
    return workInProgress.child;
  }
}

总结

beginWork的次要性能就是解决以后遍历到的fiber,通过一番解决之后返回它的子fiber,一个一个地往外吐出fiber节点,那么workInProgress树也就会被一点一点地构建进去。

这是beginWork的大抵流程,但实际上,外围更新的工作都是在各个更新函数中,这些函数会安顿fiber节点顺次进入两大解决流程:计算新状态和Diff算法,限于篇幅,这两个内容会分两篇文章具体解说,能够继续关注。

欢送扫码关注公众号,发现更多技术文章

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理