Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

剖析 React 内部运行机制 #33

Open
FE-Sadhu opened this issue Apr 9, 2024 · 0 comments
Open

剖析 React 内部运行机制 #33

FE-Sadhu opened this issue Apr 9, 2024 · 0 comments
Labels

Comments

@FE-Sadhu
Copy link
Owner

FE-Sadhu commented Apr 9, 2024

我们以首次启动项目到渲染完成为线索,从源码的角度看 React 的内部运行机制,看看 React 是如何实现我们在宏观理解 React 原理里介绍的架构。
只要创建支持并发模式的 React 项目,入口文件都会有类似这么一段代码:

// 初始化 Root 节点、render App 组件
ReactDOM.createRoot(document.getElementById('root')).render(
  <App />
)

这里就做了两件事情:

  1. React 的一些初始化工作
  2. 开始渲染组件

createRoot 做的初始化工作

我们已经在宏观理解 React 原理里讲过,Fiber 的工作原理类似于显卡的双缓冲机制,React 项目运行时内存里会存在两棵树:current Fiber 树和 wip Fiber 树,两棵树的协调管理就是通过此处要创建的 root 节点进行的。
我们可以看看 createRoot 的内部实现:

function createRoot(container) {
  // ...
  // 创建 Fiber root	节点
  const root = createFiberRoot(
    containerInfo, // DOM 容器
    tag, // 传入 ConcurrentRoot,代表需要创建一个 Concurrent Root
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
    null,
  );
  // 拦截、监听、重写 DOM 容器的事件
  listenToAllSupportedEvents(rootContainerElement);

  return new ReactDOMRoot(root);
}

createFiberRoot 内部除了创建 FiberRoot 外,还创建了 current 树的根节点 HostRoot,内部实现如下:

function createFiberRoot(containerInfo: Container, tag: RootTag, ...) {
  // 创建 FiberRoot
  const root: FiberRoot = (new FiberRootNode(
    containerInfo, // DOM 容器
    tag, // ConcurrentRoot
    hydrate,
    identifierPrefix,
    onRecoverableError,
    formState,
  ): any);

  // 创建 current 树的根节点 HostRoot
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  // ...
  // 初始化 HostRoot 的 updateQueue
  initializeUpdateQueue(uninitializedFiber);

  // 返回 FiberRoot
  return root;
}

通过此处查看 FiberRootFiber 的数据结构,本质上它们都是 JS 对象,其不同字段代表了不同意义,我们在遇到的时候再说明
初始化工作完成后,以树形描述节点如下图

渲染流程

我们可以看到 createRoot 的返回值是 ReactDOMRoot 的实例,然后执行该实例的 render 方法去渲染组件。 我们来看对应的源码:

ReactDOMRoot.prototype.render =
  // $FlowFixMe[missing-this-annot]
  function (children: ReactNodeList): void {
    const root = this._internalRoot;
    // ...
    
    updateContainer(children, root, null, null);
  };

可以看到执行 render 本质上是执行 updateContainer 方法。

JSX

这个 children 就是 jsx ,举个例子,如下代码:

ReactDOM.createRoot(document.getElementById('root')).render(
	<div id="rootDiv" key="a_1">
	  <App />
  </div>
)

会被转译成:

import { jsx as _jsx } from "react/jsx-runtime";
ReactDOM.createRoot(document.getElementById('root')).render( /*#__PURE__*/_jsx("div", {
  id: "rootDiv",
  children: /*#__PURE__*/_jsx(App, {})
}, "a_1"));

看看 jsx 方法的实现:

/**
 * https://github.com/reactjs/rfcs/pull/107
 * @param {*} type jsx 标签的类型
 * @param {object} props 属性
 * @param {string} key 用于优化的 key
 */
function jsx(type, config, maybeKey) {
	let propName;

  // 去除了保留字段后存的 props 对象
  const props = {};

  let key = null;
  let ref = null;

  if (maybeKey !== undefined) {
    // ... 开发模式的一些检查
    key = '' + maybeKey;
  }
	
	// 处理 jsx 有展开运算符的情况
  if (hasValidKey(config)) {
    // ... 开发模式的一些检查
    key = '' + config.key;
  }

  if (hasValidRef(config)) {
    ref = config.ref;
  }

  // 除了内部保留 Props 外的所有属性都构造到 props 对象里
  for (propName in config) {
    if (
      hasOwnProperty.call(config, propName) &&
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }

  // 处理 defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
	
	// 生成 React Element
  return ReactElement(
    type,
    key,
    ref,
    undefined,
    undefined,
    ReactCurrentOwner.current,
    props,
  );
}

function ReactElement(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type,
    key,
    ref,
    props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

	// ...
	
  return element;
}

可以看到,jsx 的执行结果就是 React Element,而 React Element 就是包含了 jsx 标签的类型、属性、key、ref 的对象。

创建更新

接下来看看 updateContainer 源码

function updateContainer(element: ReactElement, container: FiberRoot) {
	// ...
	
	// current HostFiber
	const current = container.current;
	// 为当前 Fiber 节点申请一个更新优先级
  const lane = requestUpdateLane(current);
  
  // ... 处理 context
  
  // 以申请好的优先级创建一个 Update 更新对象
  const update = createUpdate(lane);
  update.payload = {element};
  
  // ...
  
  // 入更新队列、把当前优先级赋予 Fiber 、返回 FiberRoot
  const root = enqueueUpdate(current, update, lane);
  if (root !== null) {
    // 协调更新
    scheduleUpdateOnFiber(root, current, lane);
    
    // ...
  }

  return lane;
}

function createUpdate(lane: Lane): Update<mixed> {
  const update: Update<mixed> = {
    lane, // 优先级

    tag: UpdateState, // 更新的类型
    payload: null, // 更新的内容
    callback: null,

    next: null, // 链表指针
  };
  return update;
}

function scheduleUpdateOnFiber(root: FiberRoot, fiber: Fiber, lane: Lane) {
	// ...
	// 标志 FiberRoot 有个 pendingLane 的更新
  markRootUpdated(root, lane, eventTime);
  // ...
  
  // 每次有更新都会执行这个方法去调度
  ensureRootIsScheduled(root);
  // ...
}

可以看到,updateContainer 主要干了这些事情:

  1. 确定更新内容、优先级,创建一个更新 update,入更新队列等待被处理
  2. 将更新优先级赋予当前 Fiber、FiberRoot
  3. 调用 ensureRootIsScheduled 检测调度更新

调度更新

再来看 ensureRootIsScheduled 是怎么调度的:

// Use this function to schedule a task for a root. There's only one task per
// root; if a task was already scheduled, we'll check to make sure the priority
// of the existing task is the same as the priority of the next level that the
// root has work on. This function is called on every update, and right before
// exiting a task.
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // callbackNode 存的是当前正在调度的 Schedule Task
  const existingCallbackNode = root.callbackNode;

  // Check if any lanes are being starved by other work. If so, mark them as
  // expired so we know to work on those next.
  // 为待处理的每个 Lane 计算过期时间,如果已有过期时间则判断有没有过期
  // 如果已过期就记录在 root.expiredLanes 里
  markStarvedLanesAsExpired(root, currentTime);

  // Determine the next lanes to work on, and their priority.
  // 确定要处理的下一批优先级
  // mount 时是 defaultLane
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  
  // 没有 Lanes 待处理
  if (nextLanes === NoLanes) {
    // Special case: There's nothing to work on.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  // We use the highest priority lane to represent the priority of the callback.
  // 挑出待处理优先级里的最高优先级
  // (只有当 nextLanes 包含纠缠Lanes 或是 TransitionLanes 或 RetryLanes 时才会再次挑出不一样的 Lane)
  const newCallbackPriority = getHighestPriorityLane(nextLanes);

  // Check if there's an existing task. We may be able to reuse it.
  // callbackPriority 存的是正在调度任务的优先级
  const existingCallbackPriority = root.callbackPriority;
  // 优先级相同则无需重新调度,不同那么 priority 只会更高
  if (
    existingCallbackPriority === newCallbackPriority
  ) {
    // The priority hasn't changed. We can reuse the existing task. Exit.
    return;
  }

  // 有更高优先级任务,现有任务则取消
  if (existingCallbackNode != null) {
    // Cancel the existing callback. We'll schedule a new one below.
    cancelCallback(existingCallbackNode);
  }

  // Schedule a new callback.
  // 调度一个新的更新
  let newCallbackNode;
  // 同步执行优先级
  if (newCallbackPriority === SyncLane) {
    // ...
    // 让处理函数 performSyncWorkOnRoot 进入一个队列。
    // PS:performSyncWorkOnRoot 处理的内容与 performConcurrentWorkOnRoot 处理内容是一样的,
    // 只不过一个是同步执行,一个是并发执行。具体源码等讲完 performConcurrentWorkOnRoot 后再来看。
    // 目前可以理解 performSyncWorkOnRoot 处理的内容就是序列化生成新的完整的 Fiber 树,然后渲染到真实视图上。
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    
    if (supportsMicrotasks) {
      // Flush the queue in a microtask.
      // ...
      // 调度一个微任务执行队列内所有回调
      scheduleMicrotask(() => {
        // In Safari, appending an iframe forces microtasks to run.
        // https://github.com/facebook/react/issues/22459
        // We don't support running callbacks in the middle of render
        // or commit so we need to check against that.
        if (
          (executionContext & (RenderContext | CommitContext)) ===
          NoContext
        ) {
          // Note that this would still prematurely flush the callbacks
          // if this happens outside render or commit phase (e.g. in an event).
          flushSyncCallbacks();
        }
      });
    } else {
      // Flush the queue in an Immediate task.
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
    newCallbackNode = null;
  } else {
    // Lane 优先级转 Scheduler 优先级
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        // Mount 时走这里,中等优先级
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    // Scheduler 模块进行调度
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority; // 正在调度的优先级
  root.callbackNode = newCallbackNode; // Schedule Task
}

时间分片任务入口

Scheduler 调度原理的分析见React Scheduler,我们来看看调度的任务做了哪些事情,也就是 performConcurrentWorkOnRoot 里做了哪些事情:

// 每个时间分片任务的入口
function performConcurrentWorkOnRoot(root, didTimeout) { 
// ...

// 拿到正在调度的 Schedule Task
const originalCallbackNode = root.callbackNode;

// ...

// TODO: This was already computed in the caller. Pass it as an argument.
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  
  // 是否用时间分片去执行的标志位
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) && // 某些高优先级的 Lanes 禁用并发
    !includesExpiredLane(root, lanes) && // 过期 Lanes 禁用并发
    // didTimeout 是为了解决饥饿问题
    // 比如一个低优先级的 work 不断被打断,到过期时间之后,这个字段为 true,
    // 意味着我等待了太久时间,等不及并发执行了
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes) // 并发
    : renderRootSync(root, lanes); // 同步
  
  // 判断 root 的更新是否处理完了
  // 如果未处理完 exitStatus === RootInProgress
  // 如果处理完了或报异常了 exitStatus !== RootInProgress
  if (exitStatus !== RootInProgress) { 
	  // ... 异常情况处理
	  
	  // 更新处理完了
	  const finishedWork: Fiber = (root.current.alternate: any);
	  root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    finishConcurrentRender(root, exitStatus, lanes);
  }
  
  // 每执行完一个切片时间,继续开启调度,处理有没有被高优先级任务打断
  ensureRootIsScheduled(root, now());
  if (root.callbackNode === originalCallbackNode) {
    // The task node scheduled for this root is the same one that's
    // currently executed. Need to return a continuation.
    // 只是切片的时间到了,没有被高优先级任务打断,继续执行该 callback
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  
  return null;
}

总结一下 performConcurrentWorkOnRoot  方法做的事情:

  1. 判断以何种方式去进行 Render 阶段处理
  2. 进入 Render 阶段处理(renderRootConcurrent | renderRootSync)
  3. 判断 Render 阶段处理结果
    1. → 若处理完了,调用 finishConcurrentRender 进入 commit 阶段,处理完 commit 阶段进入第 4 步
    2. → 若没处理完,直接进入第 4 步
  4. ensureRootIsScheduled 继续开启调度,处理有没有高优先级任务插队或是否无任务了。
    1. 若无任务,则退出执行
    2. 若被高优先级任务插队,中断当前任务触发的 Render 阶段执行,调度高优先级任务执行
    3. 若无高优先级任务插队,则继续调度处理当前任务触发的 Render 阶段

Render 阶段

接下来看看 Render 阶段到底处理了哪些事情,以 renderRootConcurrent 的源码来分析:

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext; // 标志已进入 Render 阶段

  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we'll continue where we left off.
  // 如果 root 或 lanes 改变了,那么需要创建一个新的 WIP 树
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    // ...
    prepareFreshStack(root, lanes);
  }

  do {
    try {
      // 开启一个 loop 持续构建 Fiber 树,直到时间分片时间不够
      workLoopConcurrent();
      break;
    } catch (thrownValue) {
      // ...
    }
  } while (true);

  // ... 
  // 标志 Render 阶段结束
  executionContext = prevExecutionContext;

  // 检查 WIP 是否处理完了.
  if (workInProgress !== null) {
    // ...
    // 代表 WIP 还未处理完,只是时间分片时间到了
    return RootInProgress;
  } else {
    // WIP 已处理完
    // ...
    // Set this to null to indicate there's no in-progress render.
    // 置空标志位
    workInProgressRoot = null;
    workInProgressRootRenderLanes = NoLanes;

    // Return the final exit status.
    return workInProgressRootExitStatus;
  }
}

先来看看是怎样生成一个 WIP 树的:

function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  if (workInProgress !== null) {
    // ...
  }


  workInProgressRoot = root;
  // 创建一个与 root.current 互相引用的 Fiber 节点 (rootWorkInProgress)
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  // 标志位处理
  workInProgress = rootWorkInProgress;
  workInProgressRootRenderLanes = renderLanes = lanes;
  workInProgressIsSuspended = false;
  workInProgressThrownValue = null;
  workInProgressRootDidAttachPingListener = false;
  workInProgressRootExitStatus = RootInProgress;
  workInProgressRootFatalError = null;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootInterleavedUpdatedLanes = NoLanes;
  workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;
  workInProgressRootConcurrentErrors = null;
  workInProgressRootRecoverableErrors = null;

  // 会处理更新队列,处理所有入队的 Update,把相同 Fiber 的 Update 串联成链表,
  // 把每个 Fiber 的优先级层层冒泡给 parent.childLanes 直到处理到 HostFiber
  finishQueueingConcurrentUpdates();

  return rootWorkInProgress;
}

// This is used to create an alternate fiber to do work on.
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    // 创建 fiber
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    // 建立与 current 的相互引用
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    // ...
  }

  // Reset all effects except static ones.
  // Static effects are not specific to a render.
  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.
  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
    ? null
    : {
      lanes: currentDependencies.lanes,
      firstContext: currentDependencies.firstContext,
    };

  // These will be overridden during the parent's reconciliation
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  return workInProgress;
}

创建完 WIP 树后,此时以树形描述节点如下图:



接下来执行 workLoopConcurrent 开启一个 loop 持续构建 Fiber 树:

function workLoopConcurrent() {
  // ...

  // shouldYield 会在当前帧无空闲时间时停止遍历,直到有空闲时间后继续遍历。
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

performUnitOfWork 方法做的事情就是根据当前 wip 节点去创建下一个 Fiber 节点并赋值给 workInProgress,并将 workInProgress 与已创建的 Fiber 节点连接起来构成 Fiber 树:

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  // ...
  
  // 1. 创建(或复用) 子 Fiber 节点
  // 2. 串联进 Fiber 树
  // 3. 返回创建的子 Fiber 节点
  let next = beginWork(current, unitOfWork, renderLanes);

  // 把处理了的 pendingProps 保存在其 memoizedProps 里,
  // 之后有 update 触发,对比最新 pendingProps 和 memoizedProps,
  // 若没变化则代表 Props 没变,可对应做优化
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // 若当前 Fiber 节点是叶子节点 || Fiber 节点的子节点都被处理完 completeWork 后
    // 处理当前 Fiber 节点的 completeWork
    completeUnitOfWork(unitOfWork);
  } else {
    // 处理下一个 wip 节点
    workInProgress = next;
  }

}

可以看到,核心处理方法就是 beginWork 与 completeUnitOfWork,而且 Fiber 节点的处理顺序是符合 DFS 规律的。

beginWork

// 1. 创建(或复用) 子 Fiber 节点
// 2. 串联进 Fiber 树
// 3. 返回创建的子 Fiber 节点
function beginWork(
  current: Fiber | null, // mount 阶段,无 current fiber 树
  workInProgress: Fiber, // WIP 树
  renderLanes: Lanes,
): Fiber | null {
	// ... 处理一些判断能否复用现有 current Fiber 节点的情况
	
	// 根据 tag 不同,创建不同的子 Fiber 节点 
	switch (workInProgress.tag) {
    // 函数式组件 mount 时走这个 case
		case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
    }
    // HostFiber 走这个 case
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case ...
    ...
	}
}
function updateHostRoot(current, workInProgress, renderLanes) {
	// ...

  // 找出已处理过的 State、Children
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  const prevChildren = prevState.element;
  cloneUpdateQueue(current, workInProgress);
  // 处理当前 Fiber 的更新链表,计算最新 nextState,赋于 workInProgress.memoizedState
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  // 找出最新处理的 State
  const nextState: RootState = workInProgress.memoizedState;
  const root: FiberRoot = workInProgress.stateNode;

  // ...

  // being called "element".
  // JSX 对象
  const nextChildren = nextState.element;

  // ... 
  
  if (nextChildren === prevChildren) {
    // 走复用 Fiber 节点流程
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
  
  // 协调生成 nextChildren (React Element) 对应的 Fiber,并挂载到 WIP.child 下
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);

  // 此时 WIP.child 是新生成的 Fiber
  return workInProgress.child;
}

这里最关键的是调用 reconcileChildren 生成新的 Fiber 节点并挂在到 WIP.child 下以及把子节点返回出去继续处理。
无论是哪个 tag,几乎都会走这两个步骤,只不过在走这两步前会做一些不同 tag 特殊的逻辑,比如像 HostRoot、HostComponent 就是直接找出要处理的 JSX 对象,像函数式组件 tag 会额外执行 hook 和函数,像类组件 tag 就会实例化执行 render 方法等。
接下来看看 reconcileChildren 具体是怎么创建 Fiber 的

function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  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.
    // 对于 mount 的组件 创建新 Fiber 节点挂载在 wip 树
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } 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.
    // 如有 current 节点,则 diff current ,根据 diff 结果复用(生成)新节点
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

mountChildFibers 和 reconcileChildFibers 其实本质上执行的都是同一个方法,区别在于是否需要跟踪副作用

export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

function ChildReconciler(shouldTrackSideEffects) {
  // 省略一系列方法 ...

  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.flags |= Placement | PlacementDEV;
    }
    return newFiber;
  }
  
  function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
    if (!shouldTrackSideEffects) {
      // Noop.
      return;
    }
    // 挂载在父 Fiber 的 deletions 里,打上 ChildDeletion 标志
    const deletions = returnFiber.deletions;
    if (deletions === null) {
      returnFiber.deletions = [childToDelete];
      returnFiber.flags |= ChildDeletion;
    } else {
      deletions.push(childToDelete);
    }
  }

  function deleteRemainingChildren(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
  ): null {
    if (!shouldTrackSideEffects) {
      // Noop.
      return null;
    }

    let childToDelete = currentFirstChild;
    while (childToDelete !== null) {
      deleteChild(returnFiber, childToDelete);
      childToDelete = childToDelete.sibling;
    }
    return null;
  }
  
  function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    lanes: Lanes,
  ): Fiber {
    const key = element.key;
    let child = currentFirstChild;
    while (child !== null) {
      // key 相同
      if (child.key === key) {
        const elementType = element.type;
        if (
          // type 相同
          child.elementType === elementType
          // ...
        ) {
          // 标记 current child 的兄弟节点为待删除
          deleteRemainingChildren(returnFiber, child.sibling);
          // 复用当前 current child 节点
          const existing = useFiber(child, element.props);
          coerceRef(returnFiber, child, existing, element);
          existing.return = returnFiber;
          return existing;
        }

        // type 不同
        // current child 节点及其兄弟节点为待删除
        deleteRemainingChildren(returnFiber, child);
        break;
      } else {
        // 标记删除当前 child,继续尝试找出 key、type 相同的兄弟节点
        deleteChild(returnFiber, child);
      }
      child = child.sibling;
    }
    
    // 根据 element 创建新 Fiber 对象
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    coerceRef(returnFiber, currentFirstChild, created, element);
    created.return = returnFiber;
    return created;
  }

  
  function reconcileChildFibers(
    returnFiber: Fiber, // 父 Fiber 节点
    currentFirstChild: Fiber | null, // 当前第一个 Fiber 子节点
    newChild: any, // 新子节点 -- React Element
    lanes: Lanes,
  ): 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.
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    if (typeof newChild === 'object' && newChild !== null) {
      // 单点 Diff
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            // reconcileSingleElement 为新 element 创建对应 Fiber,并串联进 wip
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        //...
      }

      // 多点 Diff
      if (isArray(newChild)) {
        return reconcileChildrenArray(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }

      // ...
    }

    // 处理文本节点
    if (
      (typeof newChild === 'string' && newChild !== '') ||
      typeof newChild === 'number'
    ) {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          '' + newChild,
          lanes,
        ),
      );
    }

    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

  return reconcileChildFibers;
}

看下怎么根据 React Element 生成 Fiber 对象的:

export function createFiberFromElement(
  element: ReactElement,
  mode: TypeOfMode,
  lanes: Lanes,
): Fiber {
  const type = element.type;
  const key = element.key;
  const pendingProps = element.props;
  const fiber = createFiberFromTypeAndProps(
    type,
    key,
    pendingProps,
    owner,
    mode,
    lanes,
  );

  return fiber;
}

export function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  lanes: Lanes,
): Fiber {
  let fiberTag = FunctionComponent;
  let resolvedType = type;
  // ... 根据不同 type 计算各种情况下合适的 fiberTag、resolvedType

  const fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.lanes = lanes;

  return fiber;
}

总结来说,reconcile children 这个协调过程就是根据 React Element 创建或复用 child fiber 的过程,如果存在 current child 节点,那么可能还会给 child fiber 打上一些 effect flag ,用于 commit 阶段做一些额外的处理比如插入或删除 DOM 节点。
我们上面讲过,workLoopConcurrent 会以 DFS 的方式处理 Fiber 节点,那么直到遇到叶子结点之前都只会进行 beginWork 处理,举以下例子:

// main.tsx
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
    <App />
)
// App.tsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

beginWork 会处理 Fiber 直到第一次遇到 img 标签,此时 Fiber 树如下图:

img 标签是叶子结点,按上述 performUnitOfWork 的逻辑,要对其执行 completeUnitOfWork 方法了。

completeWork

completeUnitOfWork 的作用就是执行当前 Fiber 的 completeWork,协调下一个 Fiber 节点的处理,分几种情况:

  1. 当前 Fiber 的 completeWork 催生了新节点,协调走新节点的 beginWork
  2. 没催生新节点,但当前 Fiber 节点有兄弟节点,协调走兄弟节点的 beginWork
  3. 没催生新节点,也没兄弟节点或兄弟节点都已处理完 completeWork,协调走父节点的 completeWork

当协调处理完所有 Fiber 节点的 completeWork 后,就完成 Render 阶段的处理了,置标志位 workInProgressRootExitStatus 为 RootCompleted。

function completeUnitOfWork(unitOfWork: Fiber): void {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork: Fiber = unitOfWork;
  do {

    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    // 处理当前节点的 completeWork 工作
    let next = completeWork(current, completedWork, renderLanes);

    // 处理当前节点的 completeWork 催生了新 work
    // (只有 Suspense API 才会走到该逻辑)
    if (next !== null) {
      workInProgress = next;
      return;
    }

    // 处理兄弟节点的 beginWork
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }
    
    // 当前节点及其兄弟节点都完成 completeWork 处理
    // 以 DFS 的规律回溯处理父节点的 completeWork
    completedWork = returnFiber;
    workInProgress = completedWork;

    // 直到处理到 HostFiber
  } while (completedWork !== null);

  // We've reached the root.
  // 完成 Render 阶段处理
  if (workInProgressRootExitStatus === RootInProgress) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

下面来看 completeWork 的内部处理

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;
 
  switch (workInProgress.tag) {
    // 省略一堆 tag 处理
    case IncompleteFunctionComponent: 
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      // 冒泡属性
      bubbleProperties(workInProgress);
      return null;
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      bubbleProperties(workInProgress);
      return null;
    }
    case HostRoot: {
      // ...
      bubbleProperties(workInProgress);
      return null;
    }
    case HostComponent: {
      // ...
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        // update 
        // 如果 prop 也没变化,则不做任何事,纯复用现成 DOM
        // 否则 workInProgress.flags |= Update;
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          renderLanes,
        );
      } else {
        // mount
        const rootContainerInstance = getRootHostContainer();
        // 创建 Fiber 对应的 DOM 节点
        const instance = createInstance(
          type,
          newProps,
          rootContainerInstance,
          currentHostContext,
          workInProgress,
        );
        // 将所有子 fiber 的 DOM 节点挂载到当前 Fiber 刚创建的 DOM 节点下
        // (体会 DFS 自下而上处理的妙处)
        appendAllChildren(instance, workInProgress, false, false);
        // 保存一份引用
        workInProgress.stateNode = instance;

        if (
          // 挂载 props 到创建的 DOM 上
          finalizeInitialChildren(
            instance,
            type,
            newProps,
            currentHostContext,
          )
        ) {
          // 有些 prop 比如自动聚焦不适合在离屏 DOM 里设置,标记 Update 到 commit 阶段挂载时再处理。
          markUpdate(workInProgress);
        }
        
      }
      // 冒泡属性
      bubbleProperties(workInProgress);

      // ... preloadInstanceAndSuspendIfNeeded
      
      return null;
    }
  }

  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      'React. Please file an issue.',
  );
}

与 beginWork 一样,completeWork 会根据 tag 不同做一些不同的处理逻辑,比如对于宿主标签就会初始化对应的 DOM 并串联起子 DOM。此外,所有 tag 也会有一个共性的处理就是 bubbleProperties 属性冒泡,冒泡的是什么属性呢? 所有子 Fiber 的优先级以及副作用处理 effect flag。分别 merge 到 fiber 的 childLanes 和 subtreeFlags 上,好处是之后(commit 阶段)不用遍历整颗 Fiber 树就能知道要处理哪些 lanes 与 effect。

function bubbleProperties(completedWork: Fiber) {
  let newChildLanes = NoLanes;
  let subtreeFlags = NoFlags;

  let child = completedWork.child;
  while (child !== null) {
    // 冒泡 lanes
    newChildLanes = mergeLanes(
      newChildLanes,
      mergeLanes(child.lanes, child.childLanes),
    );

    // 冒泡 flags
    subtreeFlags |= child.subtreeFlags;
    subtreeFlags |= child.flags;

    child = child.sibling;
  }

  completedWork.subtreeFlags |= subtreeFlags;
  completedWork.childLanes = newChildLanes;
}

到此为止,Render 阶段的核心代码我们就过完了,接下来就是 performConcurrentWorkOnRoot 里讲过的,要进入 commit 流程了:

//...
function performConcurrentWorkOnRoot(root, didTimeout) {
  // ...
  
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes) // 并发
    : renderRootSync(root, lanes); // 同步
  
  // 判断 root 的更新是否处理完了
  // 如果未处理完 exitStatus === RootInProgress
  // 如果处理完了或报异常了 exitStatus !== RootInProgress
  if (exitStatus !== RootInProgress) { 
	  // ... 异常情况处理
	  
	  // 更新处理完了
	  const finishedWork: Fiber = (root.current.alternate: any);
	  root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    finishConcurrentRender(root, exitStatus, lanes);
  }  
  
  // ...
}

Commit 阶段

function finishConcurrentRender(
  root: FiberRoot,
  exitStatus: RootExitStatus,
  finishedWork: Fiber,
  lanes: Lanes,
) {
  switch (exitStatus) {
    // ... 省略特殊 case 处理
    case RootCompleted: {
      // The work completed. Ready to commit.
      commitRoot(
        root,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
      );
      break;
    }
    default: {
      throw new Error('Unknown root exit status.');
    }
  }
}
function commitRoot(
  root: FiberRoot,
  recoverableErrors: null | Array<CapturedValue<mixed>>,
  transitions: Array<Transition> | null,
) {
   do {
    // 处理上次渲染未来得及执行的所有 useEffect 回调与其他同步任务。
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;

  // 解除标志位引用
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  root.callbackNode = null;
  root.callbackPriority = NoLane;

  // Check which lanes no longer have any work scheduled on them, and mark
  // those as finished.
  let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);

  // Make sure to account for lanes that were updated by a concurrent event
  // during the render phase; don't mark them as finished.
  const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
  remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);

  // 从 root.pendingLanes 里去掉本次 Render 阶段已处理完的 lanes
  markRootFinished(root, remainingLanes);

  // 解除标志位引用
  workInProgressRoot = null;
  workInProgress = null;
  workInProgressRootRenderLanes = NoLanes;

  // 异步调度 useEffect
  if (
      (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
      (finishedWork.flags & PassiveMask) !== NoFlags
    ) {
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        pendingPassiveEffectsRemainingLanes = remainingLanes;
        pendingPassiveTransitions = transitions;
        
        scheduleCallback(NormalSchedulerPriority, () => {
          // 处理 useEffect 回调
          flushPassiveEffects();
          return null;
        });
      }
  }

  // 整颗 Fiber 树是否有待处理的副作用
  const subtreeHasEffects =
    (finishedWork.subtreeFlags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;
  const rootHasEffect =
    (finishedWork.flags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;

  // 处理副作用
  if (subtreeHasEffects || rootHasEffect) {
    
    // ...
    
    // 标志 Commit 阶段开始
    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;

    // The first phase a "before mutation" phase. We use this phase to read the
    // state of the host tree right before we mutate it. This is where
    // getSnapshotBeforeUpdate is called.
    // 1. 处理 DOM 节点渲染/删除后的 autoFocus、blur 逻辑
    // 2. 触发 getSnapshotBeforeUpdate 调用的地方
    commitBeforeMutationEffects(
      root,
      finishedWork,
    );

    // The next phase is the mutation phase, where we mutate the host tree.
    // 根据 flags 操作真实 DOM(增删改)
    // (还会处理 useIntersectionEffect、ref 等)
    commitMutationEffects(root, finishedWork, lanes);

    // 切换 current Fiber 树
    root.current = finishedWork;

    // The next phase is the layout phase, where we call effects that read
    // the host tree after it's been mutated. The idiomatic use case for this is
    // layout, but class component lifecycles also fire here for legacy reasons.
    // 此时真实 DOM 已被修改,js 可以获取到更新后的 DOM 节点了
    // 此时主要处理的 effect 就是 useLayoutEffect 、各种 callback 如 setState 的第二个参数的调用
    commitLayoutEffects(finishedWork, root, lanes);

    // 标志 Commit 阶段结束
    executionContext = prevExecutionContext;

    // ...
  } else {
    // No effects.
    root.current = finishedWork;
  }

  
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  // 保存要处理的 passive effect 引用,异步调用时会访问这些引用
  if (rootDoesHavePassiveEffects) {
    // This commit has passive effects. Stash a reference to them. But don't
    // schedule a callback until after flushing layout work.
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsLanes = lanes;
  }

  // Always call this before exiting `commitRoot`, to ensure that any
  // additional work on this root is scheduled.
  // 可能 commit 阶段还会产生一些更新 或者 需启动更低优先级更新的处理,需再次调度 root
  ensureRootIsScheduled(root, now());

  
  // ... 

  // 执行同步任务,这样同步任务不需要等到下次事件循环再执行
  // 比如在 componentDidMount 中执行 setState 创建的更新会在这里被同步执行
  flushSyncWorkOnAllRoots();
    
  return null;
}

总结来说,commit 阶段主要做的事情如下:

  1. 协调 useEffect 的处理
  2. 移除本次更新中已处理的 Lanes
  3. 根据 effect flags 操作真实 DOM(增删改)以及一些生命周期和 API 的处理
  4. 切换 current 树为 wip 树,解除 wip 树引用
  5. 调度 root 更新

处理副作用的三个阶段

我们在代码注释里也看到了,React 是通过三个 phase 去处理副作用的,分别是 before mutation、mutation、layout。这三个阶段都会从 HostFiber 开始深度优先遍历地去找对应 effect flag 的 Fiber 节点,自下而上地去处理。
其中 before mutation 阶段做的事情主要就是:

  1. 处理 DOM 节点渲染/删除后的 autoFocus、blur 逻辑
  2. 触发 getSnapshotBeforeUpdate 调用

mutation 阶段做的事情主要是根据 effect flag 操作真实 DOM(增删改),也会处理 useIntersectionEffect、ref 等 API。
layout 阶段做的事情主要就是处理 useLayoutEffect、各种修改 DOM 后的 callback 的调用(如 setState 的第二个参数、render 第三个参数)
这三个阶段源码里处理的情况很多,这里不一一介绍,下面以首次渲染在 mutation 阶段里的处理带大家过一下源码,看看 DOM 是怎么挂载到容器的。

首次渲染的 DOM 是怎么挂载到容器的

// 在 mutation 阶段操作真实 DOM
commitMutationEffects(root, finishedWork, lanes);


export function commitMutationEffects(
  root: FiberRoot,
  finishedWork: Fiber,
  committedLanes: Lanes,
) {
  // 标志位处理
  inProgressLanes = committedLanes;
  inProgressRoot = root;
  setCurrentDebugFiberInDEV(finishedWork);
  // 处理实体
  commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
  
  setCurrentDebugFiberInDEV(finishedWork);
  inProgressLanes = null;
  inProgressRoot = null;
}


function commitMutationEffectsOnFiber(
  finishedWork: Fiber,
  root: FiberRoot,
  lanes: Lanes,
) {
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;
    
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
        recursivelyTraverseMutationEffects(root, finishedWork, lanes);
        commitReconciliationEffects(finishedWork);
    
        if (flags & Update) {
          // ... 处理 useInsertEffect
        }
        return;
    }
    case HostRoot: {
        recursivelyTraverseMutationEffects(root, finishedWork, lanes);
        commitReconciliationEffects(finishedWork);
      
        if (flags & Update) {
          // ... SSR 相关的处理
        }
      return;
    }
    // ... 一系列 case 处理
    default: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      return;
    }
 }
}

我们可以看到,几乎所有 tag 都要做的两个处理就是 recursivelyTraverseMutationEffects 与 commitReconciliationEffects。
先来看看 recursivelyTraverseMutationEffects 做了啥:

function recursivelyTraverseMutationEffects(
  root: FiberRoot,
  parentFiber: Fiber,
  lanes: Lanes,
) {
  // Deletions effects can be scheduled on any fiber type. They need to happen
  // before the children effects hae fired.
  // 增改前先递归删,免得操作到被删除 DOM 了
  const deletions = parentFiber.deletions;
  if (deletions !== null) {
    for (let i = 0; i < deletions.length; i++) {
      const childToDelete = deletions[i];
      try {
        // 删除 DOM 节点
        commitDeletionEffects(root, parentFiber, childToDelete);
      } catch (error) {
        captureCommitPhaseError(childToDelete, parentFiber, error);
      }
    }
  }
  
  if (parentFiber.subtreeFlags & MutationMask) {
    let child = parentFiber.child;
    while (child !== null) {
      // DFS
      commitMutationEffectsOnFiber(child, root, lanes);
      child = child.sibling;
    }
  }
}

直到找到叶子节点或包含 flag 的当前节点后,就会执行 commitReconciliationEffects(当前节点) 继续处理:

function commitReconciliationEffects(finishedWork: Fiber) {
  // Placement effects (insertions, reorders) can be scheduled on any fiber
  // type. They needs to happen after the children effects have fired, but
  // before the effects on this fiber have fired.
  const flags = finishedWork.flags;
  // 我们在创建 wip HostFiber 的 beginWork 时,走的是 reconcileChildFibers 逻辑,
  // 会给创建的 child fiber 打上 Placement flag
  if (flags & Placement) {
    try {
      commitPlacement(finishedWork);
    } catch (error) {
      captureCommitPhaseError(finishedWork, finishedWork.return, error);
    }
    // 去除已处理的 flag
    finishedWork.flags &= ~Placement;
  }

  // ssr
  if (flags & Hydrating) {
    finishedWork.flags &= ~Hydrating;
  }
}

在 commitPlacement 里,我们就会把创建好的离屏 DOM 直接插入到容器下:

function commitPlacement(finishedWork: Fiber): void {
  // 找到当前 Fiber 的父容器 Fiber(容器 Fiber 是指有指针指向真实 DOM 的 Fiber)
  // 此处是 HostFiber
  const parentFiber = getHostParentFiber(finishedWork);

  switch (parentFiber.tag) {
      case HostSingleton: {
        // ...
      }
      case HostComponent: {
        const parent: Instance = parentFiber.stateNode;
        if (parentFiber.flags & ContentReset) {
          // 有些标签需要在插入之前清空文本
          resetTextContent(parent);
          parentFiber.flags &= ~ContentReset;
        }
        
        const before = getHostSibling(finishedWork);
        // We only have the top Fiber that was inserted but we need to recurse down its
        // children to find all the terminal nodes.
        insertOrAppendPlacementNode(finishedWork, before, parent);
        break;
      }
      case HostRoot:
      case HostPortal: {
        // 容器真实 DOM
        const parent: Container = parentFiber.stateNode.containerInfo;
        // 固定各 renderer 提供 insertBefore 的插入 DOM 方法,web 仅支持 insertBefore API 
        // 所以需求是插入节点的话那么需要找到被插入的兄弟节点
        // 如果没兄弟节点,就是 append 添加
        const before = getHostSibling(finishedWork);
        // 执行插入或添加操作
        insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
        break;
      }
      default:
        throw new Error(
          'Invalid host parent fiber. This error is likely caused by a bug ' +
            'in React. Please file an issue.',
        );
  }
}

// 执行插入或添加操作
function insertOrAppendPlacementNodeIntoContainer(
  node: Fiber, 
  before: ?Instance,
  parent: Container,
): void {
  const {tag} = node;
  const isHost = tag === HostComponent || tag === HostText;
  if (isHost) {
    const stateNode = node.stateNode;
    if (before) {
      // renderer API 插入真实 DOM
      insertInContainerBefore(parent, stateNode, before);
    } else {
      // renderer API 添加真实 DOM
      appendChildToContainer(parent, stateNode);
    }
  } else if (
    tag === HostPortal ||
    (supportsSingletons ? tag === HostSingleton : false)
  ) {
    // ...
  } else {
    // 如果是非 DOM 节点,就往下找最近的 DOM 节点
    const child = node.child;
    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      let sibling = child.sibling;
      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

至此,页面上就有渲染好的真实 DOM 了(由于有些宿主标签的限制,会在下一帧才会绘制完整视图,但此时 js 是能拿到该视图对应的真实 DOM 的)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant