Skip to content

08.commitWork

同学们好,这一小节我们就来实现render函数中commitWork部分的逻辑。

packages/react-reconciler/src/ReactFiberWorkLoop.js

diff
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
import { completeWork } from "./ReactFiberCompleteWork";
+import { NoFlags, MutationMask } from "./ReactFiberFlags";
+import { commitMutationEffectsOnFiber } from './ReactFiberCommitWork';

let workInProgress = null;

/**
 * 在 Fiber 上计划更新根节点。
 * @param {*} root - 根节点。
 */
export function scheduleUpdateOnFiber(root) {
  ensureRootIsScheduled(root);
}

/**
 * 确保根节点被调度执行。
 * @param {*} root - 根节点。
 */
function ensureRootIsScheduled(root) {
  scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}

/**
 * 执行根节点上的并发工作。
 * @param {*} root - 根节点。
 */
function performConcurrentWorkOnRoot(root) {
  renderRootSync(root);
  const finishedWork = root.current.alternate;
  root.finishedWork = finishedWork;
  commitRoot(root);
}

/**
 * 提交根节点。
 * @param {*} root - 根节点。
 */
function commitRoot(root) {
+  const { finishedWork } = root;
+  const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
+  const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;

+  if (subtreeHasEffects || rootHasEffect) {
+    commitMutationEffectsOnFiber(finishedWork, root);
+  }

+  root.current = finishedWork;
}

/**
 * 准备一个新的工作栈。
 * @param {*} root - 根节点。
 */
function prepareFreshStack(root) {
  workInProgress = createWorkInProgress(root.current, null);
}

/**
 * 同步渲染根节点。
 * @param {*} root - 根节点。
 */
function renderRootSync(root) {
  prepareFreshStack(root);
  workLoopSync();
}

/**
 * 同步工作循环。
 */
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

/**
 * 执行一个工作单元。
 * @param {*} unitOfWork - 工作单元。
 */
function performUnitOfWork(unitOfWork) {
  const current = unitOfWork.alternate;
  const next = beginWork(current, unitOfWork);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

/**
 * 完成一个工作单元。
 * @param {*} unitOfWork - 工作单元。
 */
function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork;

  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    completeWork(current, completedWork);

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }

    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
}

packages/react-reconciler/src/ReactFiberCommitWork.js

js
import { appendChild, insertBefore } from "react-dom-bindings/src/client/ReactDOMHostConfig";
import { Placement, MutationMask } from "./ReactFiberFlags";
import { HostComponent, HostRoot, HostText } from "./ReactWorkTags";

/**
 * 递归遍历所有子节点并在每个fiber上应用mutation副作用
 * @param {Fiber} root - Fiber树的根节点
 * @param {Fiber} parentFiber - 当前fiber节点的父节点
 */
function recursivelyTraverseMutationEffects(root, parentFiber) {
  if (parentFiber.subtreeFlags & MutationMask) {
    let { child } = parentFiber;
    while (child !== null) {
      commitMutationEffectsOnFiber(child, root);
      child = child.sibling;
    }
  }
}

/**
 * 应用fiber节点上的调和副作用
 * @param {Fiber} finishedWork - 已完成的工作单位,即fiber节点
 */
function commitReconciliationEffects(finishedWork) {
  const { flags } = finishedWork;
  if (flags & Placement) {
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
}

/**
 * 判断是否为宿主父节点
 * @param {Fiber} fiber - fiber节点
 * @returns {Boolean} 是宿主父节点则返回true,否则返回false
 */
function isHostParent(fiber) {
  return fiber.tag === HostComponent || fiber.tag == HostRoot;
}

/**
 * 获取fiber节点的宿主父节点
 * @param {Fiber} fiber - fiber节点
 * @returns {Fiber} fiber节点的宿主父节点
 */
function getHostParentFiber(fiber) {
  let parent = fiber.return;
  while (parent !== null) {
    if (isHostParent(parent)) {
      return parent;
    }
    parent = parent.return;
  }
}

/**
 * 将节点插入或附加到父节点
 * @param {Fiber} node - fiber节点
 * @param {Node} before - 参考节点
 * @param {Node} parent - 父节点
 */
function insertOrAppendPlacementNode(node, before, parent) {
  const { tag } = node;
  const isHost = tag === HostComponent || tag === HostText;
  if (isHost) {
    const { stateNode } = node;
    if (before) {
      insertBefore(parent, stateNode, before);
    } else {
      appendChild(parent, stateNode);
    }
  } else {
    const { child } = node;
    if (child !== null) {
      insertOrAppendPlacementNode(child, before, parent)
      let { sibling } = child;
      while (sibling !== null) {
        insertOrAppendPlacementNode(sibling, before, parent)
        sibling = sibling.sibling;
      }
    }
  }
}

/**
 * 获取宿主兄弟节点
 * @param {Fiber} fiber - fiber节点
 * @returns {Node|null} 如果存在宿主兄弟节点则返回,否则返回null
 */
function getHostSibling(fiber) {
  let node = fiber;
  siblings: while (true) {
    while (node.sibling === null) {
      if (node.return === null || isHostParent(node.return)) {
        return null;
      }
      node = node.return;
    }
    node = node.sibling;
    while (node.tag !== HostComponent && node.tag !== HostText) {
      if (node.flags & Placement) {
        continue siblings;
      } else {
        node = node.child;
      }
    }
    if (!(node.flags & Placement)) {
      return node.stateNode;
    }
  }
}

/**
 * 提交位置
 * @param {Fiber} finishedWork - 已完成的工作单位,即fiber节点
 */
function commitPlacement(finishedWork) {
  const parentFiber = getHostParentFiber(finishedWork);
  switch (parentFiber.tag) {
    case HostRoot: {
      const parent = parentFiber.stateNode.containerInfo;
      const before = getHostSibling(finishedWork);
      insertOrAppendPlacementNode(finishedWork, before, parent);
      break;
    }
    case HostComponent: {
      const parent = parentFiber.stateNode;
      const before = getHostSibling(finishedWork);
      insertOrAppendPlacementNode(finishedWork, before, parent);
      break;
    }
  }
}

/**
 * 遍历fiber树并在每个fiber上应用mutation副作用
 * @param {Fiber} finishedWork - 已完成的工作单位,即fiber节点
 * @param {Fiber} root - fiber树的根节点
 */
export function commitMutationEffectsOnFiber(finishedWork, root) {
  switch (finishedWork.tag) {
    case HostRoot:
    case HostComponent:
    case HostText: {
      recursivelyTraverseMutationEffects(root, finishedWork);
      commitReconciliationEffects(finishedWork);
      break;
    }
  }
}

packages/react-dom-bindings/src/client/ReactDOMHostConfig.js

diff
// 引入初始属性设置函数
import { setInitialProperties } from './ReactDOMComponent';

/**
 * 判断是否需要设置文本内容
 *
 * @param {string} type - DOM元素的类型
 * @param {Object} props - 元素属性对象
 * @return {boolean} - 如果children属性是字符串或数字,返回true,否则返回false
 *
 * shouldSetTextContent函数用于判断基于给定的属性,是否应该设置DOM元素的文本内容。
 */
export function shouldSetTextContent(type, props) {
  return typeof props.children === "string" || typeof props.children === "number";
}

/**
 * 创建文本节点实例
 *
 * @param {string} content - 文本内容
 * @return {Text} - 创建的文本节点
 *
 * createTextInstance函数用于创建一个新的文本节点,其中的内容是传入的content参数。
 */
export function createTextInstance(content) {
  return document.createTextNode(content);
}

/**
 * 创建DOM元素实例
 *
 * @param {string} type - DOM元素的类型
 * @return {HTMLElement} - 创建的DOM元素
 *
 * createInstance函数用于创建一个新的DOM元素,元素类型由传入的type参数指定。
 */
export function createInstance(type) {
  const domElement = document.createElement(type);
  return domElement;
}

/**
 * 将初始子节点附加到父节点
 *
 * @param {HTMLElement} parent - 父节点
 * @param {HTMLElement|Text} child - 子节点
 *
 * appendInitialChild函数用于将子节点附加到指定的父节点。
 */
export function appendInitialChild(parent, child) {
  parent.appendChild(child);
}

/**
 * 为DOM元素设置初始属性
 *
 * @param {HTMLElement} domElement - 目标DOM元素
 * @param {string} type - DOM元素的类型
 * @param {Object} props - 需要设置的属性对象
 *
 * finalizeInitialChildren函数用于在DOM元素创建完成后,设置其初始属性。
 */
export function finalizeInitialChildren(domElement, type, props) {
  setInitialProperties(domElement, type, props);
}

+/**
+ * 将子节点附加到父节点
+ *
+ * @param {HTMLElement} parentInstance - 父节点
+ * @param {HTMLElement|Text} child - 子节点
+ *
+ * appendChild函数用于将子节点附加到指定的父节点。
+ */
+export function appendChild(parentInstance, child) {
+  parentInstance.appendChild(child);
+}

+/**
+ * 在指定子节点前插入新的子节点
+ *
+ * @param {HTMLElement} parentInstance - 父节点
+ * @param {HTMLElement|Text} child - 需要插入的新子节点
+ * @param {HTMLElement|Text} beforeChild - 指定的子节点
+ *
+ * insertBefore函数用于在父节点的指定子节点前插入一个新的子节点。
+ */
+export function insertBefore(parentInstance, child, beforeChild) {
+  parentInstance.insertBefore(child, beforeChild);
+}

基于 VitePress 构建