Skip to content

07.completeWork

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

packages/react-reconciler/src/ReactFiberWorkLoop.js

diff
import { scheduleCallback } from "scheduler";
import { createWorkInProgress } from "./ReactFiber";
import { beginWork } from "./ReactFiberBeginWork";
+import { completeWork } from "./ReactFiberCompleteWork";

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);
+ commitRoot(root);
}

+/**
+ * 提交根节点。
+ * @param {*} root - 根节点。
+ */
+function commitRoot(root) {
+  console.log('开始commitWork')
+}

/**
 * 准备一个新的工作栈。
 * @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;
- workInProgress = null //临时加上,防止死循环 
  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

/**
 * 完成一个工作单元。
 * @param {*} unitOfWork - 工作单元。
 */
function completeUnitOfWork(unitOfWork) {
- console.log('开启comleteWork阶段')
+  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/ReactFiberCompleteWork.js

js
import {
  createTextInstance,
  createInstance,
  appendInitialChild,
  finalizeInitialChildren
} from 'react-dom-bindings/src/client/ReactDOMHostConfig';
import { NoFlags } from "./ReactFiberFlags";
import { HostComponent, HostRoot, HostText } from "./ReactWorkTags";

/**
 * 为完成的fiber节点的父DOM节点添加所有子DOM节点
 * @param {DOM} parent - 完成的fiber节点对应的真实DOM节点
 * @param {Fiber} workInProgress - 已完成的Fiber节点
 */
function appendAllChildren(parent, workInProgress) {
  let node = workInProgress.child;
  while (node) {
    if (node.tag === HostComponent || node.tag === HostText) {
      appendInitialChild(parent, node.stateNode);
    } else if (node.child !== null) {
      node = node.child;
      continue;
    }
    if (node === workInProgress) {
      return;
    }
    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return;
      }
      node = node.return;
    }
    node = node.sibling;
  }
}

/**
 * 完成一个Fiber节点
 * @param {Fiber} current - 当前旧的Fiber节点
 * @param {Fiber} workInProgress - 新建的Fiber节点
 */
export function completeWork(current, workInProgress) {
  const newProps = workInProgress.pendingProps;
  switch (workInProgress.tag) {
    case HostRoot:
      bubbleProperties(workInProgress);
      break;
    case HostComponent:
      const { type } = workInProgress;
      const instance = createInstance(type, newProps, workInProgress);
      appendAllChildren(instance, workInProgress);
      workInProgress.stateNode = instance;
      finalizeInitialChildren(instance, type, newProps);
      bubbleProperties(workInProgress);
      break;
    case HostText:
      const newText = newProps;
      workInProgress.stateNode = createTextInstance(newText);
      bubbleProperties(workInProgress);
      break;
  }
}

/**
 * 冒泡处理已完成Fiber节点的属性
 * @param {Fiber} completedWork - 已完成的Fiber节点
 */
function bubbleProperties(completedWork) {
  let subtreeFlags = NoFlags;
  let child = completedWork.child;
  while (child !== null) {
    subtreeFlags |= child.subtreeFlags;
    subtreeFlags |= child.flags;
    child = child.sibling;
  }
  completedWork.subtreeFlags = subtreeFlags;
}

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);
+}

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

js
// 引入CSS样式设置函数
import { setValueForStyles } from './CSSPropertyOperations';
// 引入文本设置函数
import setTextContent from './setTextContent';
// 引入DOM属性设置函数
import { setValueForProperty } from './DOMPropertyOperations';


/**
 * 设置初始DOM属性
 * 
 * @param {string} tag - DOM元素的标签名
 * @param {HTMLElement} domElement - 目标DOM元素
 * @param {Object} nextProps - 需要设置的属性对象
 *
 * setInitialDOMProperties函数用于设置目标DOM元素的初始属性。它遍历nextProps对象中的所有属性,
 * 对于'style'属性,使用setValueForStyles函数设置DOM元素的样式;
 * 对于'children'属性,根据属性值的类型(字符串或数字),使用setTextContent函数设置DOM元素的文本内容;
 * 对于其他非空属性,使用setValueForProperty函数设置DOM元素的对应属性。
 */
function setInitialDOMProperties(tag, domElement, nextProps) {
  for (const propKey in nextProps) {
    if (nextProps.hasOwnProperty(propKey)) {
      const nextProp = nextProps[propKey];
      if (propKey === 'style') {
        setValueForStyles(domElement, nextProp);
      } else if (propKey == 'children') {
        if (typeof nextProp === 'string') {
          setTextContent(domElement, nextProp);
        } else if (typeof nextProp === 'number') {
          setTextContent(domElement, `${nextProp}`);
        }
      } else if (nextProp !== null) {
        setValueForProperty(domElement, propKey, nextProp)
      }
    }
  }
}

/**
 * 设置初始属性
 *
 * @param {HTMLElement} domElement - 目标DOM元素
 * @param {string} tag - DOM元素的标签名
 * @param {Object} props - 需要设置的属性对象
 *
 * setInitialProperties函数是setInitialDOMProperties函数的外部接口,它直接调用setInitialDOMProperties函数,
 * 将传入的参数domElement, tag, props作为参数传递给setInitialDOMProperties函数。
 */
export function setInitialProperties(domElement, tag, props) {
  setInitialDOMProperties(tag, domElement, props);
}

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

js
/**
 * 设置节点的样式
 *
 * @param {HTMLElement} node - 目标节点
 * @param {Object} styles - 包含样式属性和值的对象
 *
 * setValueForStyles函数用于遍历传入的styles对象,并将其属性值应用到目标节点的style属性中。
 * 该函数首先获取节点的style属性,然后遍历styles对象。如果styles对象有对应的样式属性,则获取对应的样式值,
 * 并将该样式值应用到节点的style属性中。
 */
export function setValueForStyles(node, styles) {
  const { style } = node;

  for (const styleName in styles) {
    if (styles.hasOwnProperty(styleName)) {
      const styleValue = styles[styleName];
      style[styleName] = styleValue;
    }
  }
}

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

js
/**
 * 设置节点的文本内容
 *
 * @param {HTMLElement} node - 需要设置文本内容的DOM节点
 * @param {string} text - 需要设置的文本内容
 *
 * setTextContent函数用于设置指定DOM节点的文本内容。
 */
function setTextContent(node, text) {
  node.textContent = text;
}

// 导出setTextContent函数
export default setTextContent;

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

js
/**
 * 设置节点的属性
 *
 * @param {HTMLElement} node - 目标节点
 * @param {string} name - 属性名
 * @param {*} value - 属性值
 *
 * setValueForProperty函数用于设置目标节点的属性。如果传入的属性值为null,
 * 则会移除节点的对应属性,否则,会将属性值设置到节点的对应属性上。
 */
export function setValueForProperty(node, name, value) {
  if (value === null) {
    node.removeAttribute(name);
  } else {
    node.setAttribute(name, value);
  }
}

基于 VitePress 构建