React 16 源码解析笔记 06 - render阶段04 构建其余节点

构建其余节点

上面已经看完beginWork,是从父到子,构建子级节点的 fiber 对象,这是递归的 “递”阶段

接着解析 completeUnitOfWork,这是从子到父,构建其余节点的 fiber 对象,是递归的 “归”阶段

completeUnitOfWork不仅仅是构建其余节点的 fiber 对象,它在从子级到父级的过程中会经过每一个 fiber 节点对象,主要完成的事情包括:

  1. 构建其余节点的 fiber 对象
  2. 为每个 fiber 节点对象构建对应的真实 DOM 对象,并添加到 stateNode 属性中
  3. 收集要执行 DOM 操作的 fiber 节点,组建 effect 链表结构
    1. 过程中,不断收集当前 fiber 对象要执行 DOM 操作的子 fiber
    2. 最后将所有要执行 DOM 操作的 fiber 节点对象都挂载到顶层 rootFiber 对象中

completeUnitOfWork

// packages\react-reconciler\src\ReactFiberWorkLoop.js
/**
 * 1. 创建 Fiber 对象
 * 2. 创建每一个节点的真实 DOM 对象并将其添加到 stateNode 属性中
 * 3. 收集要执行 DOM 操作的 Fiber 节点, 组建 effect 链表结构
 */
function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
  // 为 workInProgress 全局变量重新赋值
  workInProgress = unitOfWork;
  do {
    // 获取备份节点
    // 初始化渲染 非根 Fiber 对象没有备份节点 所以 current 为 null
    const current = workInProgress.alternate;
    // 父级 Fiber 对象, 非根 Fiber 对象都有父级
    const returnFiber = workInProgress.return;
    // 判断传入的 Fiber 对象是否构建完成, 任务调度相关
    // & 是表示位的与运算, 把左右两边的数字转化为二进制
    // 然后每一位分别进行比较, 如果相等就为1, 不相等即为0
    // 此处应用"位与"运算符的目的是"清零"
    // true
    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      // 开发环境代码 忽略
      setCurrentDebugFiberInDEV(workInProgress);
      let next;
      // 如果不能使用分析器的 timer, 直接执行 completeWork
      // enableProfilerTimer => true
      // 但此处无论条件是否成立都会执行 completeWork
      if (
        !enableProfilerTimer ||
        (workInProgress.mode & ProfileMode) === NoMode
      ) {
        // 重点代码(二)
        // 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
        next = completeWork(current, workInProgress, renderExpirationTime);
      } else {
        // 否则执行分析器timer, 并执行 completeWork
        startProfilerTimer(workInProgress);
        // 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
        next = completeWork(current, workInProgress, renderExpirationTime);
        // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
      }
      stopWorkTimer(workInProgress);
      resetCurrentDebugFiberInDEV();
      resetChildExpirationTime(workInProgress);
      // 重点代码(一)
      // 如果子级存在
      if (next !== null) {
        // 返回子级 一直返回到 workLoopSync
        // 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象
        return next;
      }

      // 构建 effect 链表结构
      // 如果不是根 Fiber 就是 true 否则就是 false
      // 将子树和此 Fiber 的所有 effect 附加到父级的 effect 列表中
      if (
        // 如果父 Fiber 存在 并且
        returnFiber !== null &&
        // 父 Fiber 对象中的 effectTag 为 0
        (returnFiber.effectTag & Incomplete) === NoEffect
      ) {
        // 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上

        // 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiber
        if (returnFiber.firstEffect === null) {
          // first
          returnFiber.firstEffect = workInProgress.firstEffect;
        }

        if (workInProgress.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            // next
            returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
          }
          // last
          returnFiber.lastEffect = workInProgress.lastEffect;
        }

        // 获取副作用标记
        // 初始渲染时除[根组件]以外的 Fiber, effectTag 值都为 0, 即不需要执行任何真实DOM操作
        // 根组件的 effectTag 值为 3, 即需要将此节点对应的真实DOM对象添加到页面中
        const effectTag = workInProgress.effectTag;

        // 创建 effect 列表时跳过 NoWork(0) 和 PerformedWork(1) 标记
        // PerformedWork 由 React DevTools 读取, 不提交
        // 初始渲染时 只有遍历到了根组件 判断条件才能成立, 将 effect 链表添加到 rootFiber
        // 初始渲染 FiberRoot 对象中的 firstEffect 和 lastEffect 都是 App 组件
        // 因为当所有节点在内存中构建完成后, 只需要一次将所有 DOM 添加到页面中
        if (effectTag > PerformedWork) {
          // false
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress;
          } else {
            // 为 fiberRoot 添加 firstEffect
            returnFiber.firstEffect = workInProgress;
          }
          // 为 fiberRoot 添加 lastEffect
          returnFiber.lastEffect = workInProgress;
        }
      }
    } else {
      // 初始渲染不执行
      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      const next = unwindWork(workInProgress, renderExpirationTime);

      // Because this fiber did not complete, don't reset its expiration time.

      if (
        enableProfilerTimer &&
        (workInProgress.mode & ProfileMode) !== NoMode
      ) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);

        // Include the time spent working on failed children before continuing.
        let actualDuration = workInProgress.actualDuration;
        let child = workInProgress.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        workInProgress.actualDuration = actualDuration;
      }

      if (next !== null) {
        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        // TODO: The name stopFailedWorkTimer is misleading because Suspense
        // also captures and restarts.
        stopFailedWorkTimer(workInProgress);
        next.effectTag &= HostEffectMask;
        return next;
      }
      stopWorkTimer(workInProgress);

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        returnFiber.firstEffect = returnFiber.lastEffect = null;
        returnFiber.effectTag |= Incomplete;
      }
    }
    // 获取下一个同级 Fiber 对象
    const siblingFiber = workInProgress.sibling;
    // 如果下一个同级 Fiber 对象存在
    if (siblingFiber !== null) {
      // 返回下一个同级 Fiber 对象
      return siblingFiber;
    }
    // 否则退回父级
    workInProgress = returnFiber;
  } while (workInProgress !== null);

  // 当执行到这里的时候, 说明遍历到了 root 节点, 已完成遍历
  // 更新 workInProgressRootExitStatus 的状态为 已完成
  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
  return null;
}

从子到父的流程

该方法整体就是一个 do...while 循环。

  • 在循环中首先判断是否有子级
    • 如果有子级,就返回子级所对应的 fiber 对象,去构建子级的子级
  • 如果没有子级,就会判断是否有同级
    • 如果有同级,就返回同级,去构建同级的子级
  • 如果没有同级,就会返回父级,判断父级是否有同级
    • 如果父级有同级,就返回父级的同级,去构建它的子级
    • 如果没有同级,返回父级的父级,依次向上直到 rootFiber

相关代码:

function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
  // 为 workInProgress 全局变量重新赋值
  workInProgress = unitOfWork;
  do {
    /* if_else start*/
    // 如果子级存在
    if (next !== null) {
      // 返回子级 一直返回到 workLoopSync
      // 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象
      return next;
    }
    /* if_else end*/
    
    // 获取下一个同级 Fiber 对象
    const siblingFiber = workInProgress.sibling;
    // 如果下一个同级 Fiber 对象存在
    if (siblingFiber !== null) {
      // 返回下一个同级 Fiber 对象
      return siblingFiber;
    }
    
    // 否则退回父级
    workInProgress = returnFiber;
  } while (workInProgress !== null);
}

创建节点真实 DOM 对象并将其添加到 stateNode 属性中

通过调用 completeWork

// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
next = completeWork(current, workInProgress, renderExpirationTime);

completeWork

completeWork 内部就是一个 switch,匹配当前节点的类型 tag

因为并不是所有的 fiber 节点,都能创建对应的 DOM 对象,例如最先判断的一些类型,直接返回 null

  • ClassComponent 类组件
  • HostRoot root 节点
  • HostComponent 普通的 React 元素

这里只解析下普通 React 元素的处理:

  • 最终会调用 createInstance方法,创建当前节点的实例对象,即真实的 DOM 对象。
  • 然后调用 appendAllChildren构建当前节点所有子级的真实 DOM 对象,追加到当前节点中
  • 当构建完 DOM 对象后,将当前节点的 DOM 对象添加到 fiber 对象的 stateNode 属性中
// packages\react-reconciler\src\ReactFiberCompleteWork.js
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  // 获取待更新 props
  const newProps = workInProgress.pendingProps;
  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    // 0
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent:/*...*/
    // 3
    case HostRoot:/*...*/
    // 5
    case HostComponent: {
      popHostContext(workInProgress);
      // 获取 rootDOM 节点 <div id="root"></div>
      const rootContainerInstance = getRootHostContainer();
      // 节点的具体的类型 div span ...
      const type = workInProgress.type;
      // 初始渲染不执行 current = null
      if (current !== null && workInProgress.stateNode != null) {
        /*...*/
      } else {
        /*...*/
        // 服务器渲染相关 初始渲染为不执行
        // false
        if (wasHydrated) {
          /*...*/
        } else {
          // 创建节点实例对象 <div></div> <span></span>
          let instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
          /**
           * 将所有的子级追加到父级中
           * instance 为父级
           * workInProgress.child 为子级
           */
          appendAllChildren(instance, workInProgress, false, false);

          // 为 Fiber 对象添加 stateNode 属性
          workInProgress.stateNode = instance;
          // 初始渲染不执行
          // false
          if (enableDeprecatedFlareAPI) {
            const listeners = newProps.DEPRECATED_flareListeners;
            if (listeners != null) {
              updateDeprecatedEventListeners(
                listeners,
                workInProgress,
                rootContainerInstance,
              );
            }
          }

          /*...*/
        }
        // 处理 ref DOM 引用
        if (workInProgress.ref !== null) {
          // If there is a ref on a host node we need to schedule a callback
          markRef(workInProgress);
        }
      }
      return null;
    }
    // 6
    case HostText:/*...*/
    case SuspenseComponent:/*...*/
    case HostPortal:/*...*/
    case ContextProvider:/*...*/
    case IncompleteClassComponent:/*...*/
    case SuspenseListComponent:/*...*/
    case FundamentalComponent:/*...*/
    case ScopeComponent:/*...*/
    case Block:/*...*/
  }
}

appendAllChildren

  • 获取当前节点的子级节点,进入 while 循环
  • 判断是否是普通元素或文本节点
    • 如果是,调用 appendInitialChild 将子级追加到父级中
    • 如果不是,则将其视为组件,获取组件的第一个子元素,进入下一个循环
      • 组件本身不能转换为真实 DOM 元素
  • 子级追加到父级后,判断是否有兄弟节点
    • 如果有,给兄弟节点指定父节点 return,并获取兄弟节点,进入下一个循环
    • 如果没有,则判断是否有父级,或父级为最初传入的节点
      • 如果满足条件,则表示已将所有子级追加到父级中
      • 如果不满足,则可能是组件,获取当前节点的父节点,继续判断它的兄弟节点
// packages\react-reconciler\src\ReactFiberCompleteWork.js
// 将所有子级追到到父级中
appendAllChildren = function (
parent: Instance,
 workInProgress: Fiber,
 needsVisibilityToggle: boolean,
 isHidden: boolean,
) {
  // 获取子级
  let node = workInProgress.child;
  // 如果子级不为空 执行循环
  while (node !== null) {
    // 如果 node 是普通 ReactElement 或者为文本
    if (node.tag === HostComponent || node.tag === HostText) {
      // 将子级追加到父级中
      appendInitialChild(parent, node.stateNode);
    } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
      appendInitialChild(parent, node.stateNode.instance);
    } else if (node.tag === HostPortal) {
      // If we have a portal child, then we don't want to traverse
      // down its children. Instead, we'll get insertions from each child in
      // the portal directly.
    } else if (node.child !== null) {
      // 如果 node 不是普通 ReactElement 又不是文本
      // 将 node 视为组件, 组件本身不能转换为真实 DOM 元素
      // 获取到组件的第一个子元素, 继续执行循环
      node.child.return = node;
      node = node.child;
      continue;
    }
    // 如果 node 和 workInProgress 是同一个节点
    // 说明 node 已经退回到父级 终止循环
    // 说明此时所有子级都已经追加到父级中了
    if (node === workInProgress) {
      return;
    }
    // 处理子级节点的兄弟节点
    while (node.sibling === null) {
      // 如果节点没有父级或者节点的父级是自己, 退出循环
      // 说明此时所有子级都已经追加到父级中了
      if (node.return === null || node.return === workInProgress) {
        return;
      }
      // 更新 node
      node = node.return;
    }
    // 更新父级 方便回退
    node.sibling.return = node.return;
    // 将 node 更新为下一个兄弟节点
    node = node.sibling;
  }
};

收集所有要执行 DOM 操作的 fiber 节点对象

React 是使用一个链表结构收集所有要执行 DOM 操作的 fiber 节点对象的。

  • firstEffect 存储第一个要执行的操作
  • lastEffect 存储最后一个要执行的操作
  • nextEffect 存储下一个要执行的操作
    • 存储在 lastEffect 下

firstEffect 和 lastEffect 如何定义的为作解析。

// 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上

// 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiber
if (returnFiber.firstEffect === null) {
  // first
  returnFiber.firstEffect = workInProgress.firstEffect;
}

if (workInProgress.lastEffect !== null) {
  if (returnFiber.lastEffect !== null) {
    // next
    returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
  }
  // last
  returnFiber.lastEffect = workInProgress.lastEffect;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值