Skip to content

04.createRoot

同学们好,前面我们说本章的目标是实现初始化渲染,具体而言,这个初始化渲染过程完成后,可以在页面上渲染一个带有一定样式的字符串:

index.html:

js
import { createRoot } from 'react-dom/client';
let element = <div>
    <div>课程名称:手写React高质量源码迈向高阶开发</div>
    <div>讲师:杨艺韬</div>
    <div>电子书:<a style={{ color: 'blue' }} href="https://www.yangyitao.com/react18">https://www.yangyitao.com/react18</a></div>
</div>
const root = createRoot(document.getElementById('root'))
root.render(element)
console.log("index.jsx", element);

我们在这里用到了jsx、createRoot、render这样几个函数,上一小节我们已经介绍了jsx,那么还剩下createRoot和render两个函数需要我们实现。刚才我们说初始化渲染完成后可以在页面上渲染一个字符串,看起来很简单,但实际上在fiber架构下,里面的逻辑是比较丰富的。

本小节会实现createRoot函数以及render函数的局部逻辑,至于render函数细节在后面的小节进行讲解:

在编写createRoot代码之前,大家回想一下我们在编写原始版react初始化的时候,写过一个函数ReactDOM.render,既然有这个函数,为什么现在要实现调用createRoot返回一个对象,再调用render函数,这两者有什么区别?

  • ReactDOM.render 是 React 的传统渲染方法,自 React 诞生以来就存在。它在同步模式下运行,即所有组件的更新和渲染都是同步执行的,一气呵成没有中断的。

  • ReactDOM.createRoot 是 React 18 引入的新方法,其最主要的特征是它允许我们在并发模式下运行 React 应用。并发模式允许 React 在渲染和更新组件时利用时间切片,使得渲染过程是可中断的,从而提高应用程序的响应性和性能,前面我们在介绍Fiber架构体系的时候也提到过。

这就像是,就像是去往同一个目的地的两种不同方式。

react-dom/src/client/ReactDOMRoot.js

js
import {
  createContainer,
  updateContainer
} from 'react-reconciler/src/ReactFiberReconciler';

/**
 * ReactDOMRoot构造函数
 *
 * @param {Object} internalRoot - React Fiber树的根节点
 *
 * 这个构造函数用于创建ReactDOMRoot实例对象,实例对象中包含了一个_internalRoot属性,该属性引用了React Fiber树的根节点。
 */
function ReactDOMRoot(internalRoot) {
  this._internalRoot = internalRoot;
}

/**
 * render方法,负责更新或渲染React组件树
 *
 * @param {ReactElement|ReactComponent} children - 需要渲染的React元素或组件
 *
 * render方法是挂载在ReactDOMRoot原型链上的,每个ReactDOMRoot实例对象都可以调用这个方法。
 * 当调用render方法时,会通过调用updateContainer函数,将传入的React元素或组件(children参数)更新或渲染到当前的Fiber树(_internalRoot属性对应的Fiber树)中。
 */
ReactDOMRoot.prototype.render = function (children) {
  const root = this._internalRoot;
  updateContainer(children, root);
}

/**
 * 创建Fiber根节点并封装为ReactDOMRoot对象的工厂函数
 *
 * @param {HTMLElement} container - React组件需要渲染到的DOM元素
 * @returns {ReactDOMRoot} 封装了Fiber根节点的ReactDOMRoot对象
 *
 * createRoot是一个工厂函数,接收一个DOM元素作为参数,这个DOM元素通常是React应用的根DOM节点。
 * 在函数内部,首先通过调用createContainer函数,传入DOM元素参数,创建一个Fiber根节点。
 * 然后将这个Fiber根节点传入ReactDOMRoot构造函数,创建一个ReactDOMRoot实例对象,并返回。
 */
export function createRoot(container) {
  const root = createContainer(container);
  return new ReactDOMRoot(root);
}

react-reconciler/src/ReactFiberReconciler.js:

js
import { createFiberRoot } from "./ReactFiberRoot";
import { createUpdate, enqueueUpdate } from "./ReactFiberClassUpdateQueue";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";

/**
 * 创建容器,用于将虚拟DOM转换为真实DOM并插入到容器中。
 * @param {*} containerInfo - DOM容器信息。
 * @returns {FiberRoot} - 创建的Fiber根节点。
 */
export function createContainer(containerInfo) {
  return createFiberRoot(containerInfo);
}

/**
 * 更新容器,将虚拟DOM转换为真实DOM并插入到容器中。
 * @param {*} element - 虚拟DOM元素。
 * @param {*} container - DOM容器,FiberRootNode。
 */
export function updateContainer(element, container) {
  // 获取当前的根Fiber
  const current = container.current;
  // 创建更新
  const update = createUpdate();
  // 要更新的虚拟DOM
  update.payload = { element };
  // 将更新添加到当前根Fiber的更新队列上,并返回根节点
  const root = enqueueUpdate(current, update);
  // 在根Fiber上调度更新
  scheduleUpdateOnFiber(root);
}

packages/react-reconciler/src/ReactFiberRoot.js:

js
import { createHostRootFiber } from './ReactFiber';
import { initialUpdateQueue } from './ReactFiberClassUpdateQueue';

/**
 * Fiber 根节点对象构造函数。
 * @param {any} containerInfo - 容器信息。
 */
function FiberRootNode(containerInfo) {
  this.containerInfo = containerInfo; // 容器信息,如 div#root
}

/**
 * 创建 Fiber 根节点。
 * @param {any} containerInfo - 容器信息。
 * @returns {FiberRootNode} - 创建的 Fiber 根节点。
 */
export function createFiberRoot(containerInfo) {
  const root = new FiberRootNode(containerInfo);
  // 创建未初始化的根 Fiber
  const uninitializedFiber = createHostRootFiber();
  // 根容器的 current 指向当前的根 Fiber
  root.current = uninitializedFiber;
  // 根 Fiber 的 stateNode,即真实 DOM 节点,指向 FiberRootNode
  uninitializedFiber.stateNode = root;
  // 初始化根 Fiber 的更新队列
  initialUpdateQueue(uninitializedFiber);
  return root;
}

packages/react-reconciler/src/ReactFiber.js:

TODO:回顾Fiber是什么,Fiber相当于是对虚拟DOM的抽象,它不仅包含了 DOM 节点的信息,还包含了这个节点在 Fiber 架构下的其他信息(如子节点、兄弟节点、父节点等)。这种抽象使得 React 能够实现更为复杂的功能,如时间切片(time-slicing)和 Suspense。

js
// 导入React中的一些工作标签和标记
import { HostComponent, HostRoot, IndeterminateComponent, HostText } from "./ReactWorkTags";
import { NoFlags } from "./ReactFiberFlags";

/**
 * 构造函数,用于创建一个新的Fiber节点
 * @param {number} tag - fiber的类型,如函数组件、类组件、原生组件、根元素等
 * @param {*} pendingProps - 新属性,等待处理或者说生效的属性
 * @param {*} key - 唯一标识
 */
export function FiberNode(tag, pendingProps, key) {
  this.tag = tag;
  this.key = key;
  this.type = null; 
  this.stateNode = null; 
  this.return = null; 
  this.child = null;
  this.sibling = null;
  this.pendingProps = pendingProps; 
  this.memoizedProps = null; 
  this.memoizedState = null;
  this.updateQueue = null;
  this.flags = NoFlags; 
  this.subtreeFlags = NoFlags;
  this.alternate = null;
  this.index = 0;
}

/**
 * 用于创建新的Fiber节点
 * @param {number} tag - fiber的类型
 * @param {*} pendingProps - 新属性
 * @param {*} key - 唯一标识
 * @returns {FiberNode} 新的Fiber节点
 */
export function createFiber(tag, pendingProps, key) {
  return new FiberNode(tag, pendingProps, key);
}

/**
 * 创建新的HostRoot类型的Fiber节点
 * @returns {FiberNode} 新的HostRoot类型的Fiber节点
 */
export function createHostRootFiber() {
  return createFiber(HostRoot, null, null);
}

/**
 * 基于旧的Fiber节点和新的属性创建一个新的Fiber节点
 * @param {FiberNode} current - 旧的Fiber节点
 * @param {*} pendingProps - 新的属性
 * @returns {FiberNode} 新的Fiber节点
 */
export function createWorkInProgress(current, pendingProps) {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    workInProgress = createFiber(current.tag, pendingProps, current.key);
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;
    workInProgress.type = current.type;
    workInProgress.flags = NoFlags;
    workInProgress.subtreeFlags = NoFlags;
  }
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  return workInProgress;
}

/**
 * 从虚拟DOM创建新的Fiber节点
 * @param {*} element - 虚拟DOM元素
 * @returns {FiberNode} 新的Fiber节点
 */
export function createFiberFromElement(element) {
  const { type, key, props: pendingProps } = element;
  return createFiberFromTypeAndProps(type, key, pendingProps);
}

/**
 * 从类型和属性创建新的Fiber节点
 * @param {*} type - Fiber节点的类型
 * @param {*} key - 唯一标识
 * @param {*} pendingProps - 新的属性
 * @returns {FiberNode} 新的Fiber节点
 */
function createFiberFromTypeAndProps(type, key, pendingProps) {
  let tag = IndeterminateComponent;
  if (typeof type === "string") {
    tag = HostComponent;
  }
  const fiber = createFiber(tag, pendingProps, key);
  fiber.type = type;
  return fiber;
}

/**
 * 创建一个新的文本类型的Fiber节点
 * @param {*} content - 文本内容
 * @returns {FiberNode} 新的文本类型的Fiber节点
 */
export function createFiberFromText(content) {
  return createFiber(HostText, content, null);
}

packages/react-reconciler/src/ReactWorkTags.js:

js
export const FunctionComponent = 0;  // 表示函数式组件,这是 React 中最基础的组件类型,通过函数返回 UI 结构
export const ClassComponent = 1;  // 表示类组件,这是 React 的另一种主要组件类型,通过 class 定义,可以使用生命周期方法等更复杂的特性
export const IndeterminateComponent = 2;  // 表示尚未确定类型的组件,在 React 渲染过程中,如果遇到了这种类型,会先尝试将其当做函数式组件处理
export const HostRoot = 3;  // 表示宿主环境的根节点,例如在浏览器环境中,这个就代表了整个 React App 的根节点
export const HostComponent = 5;  // 表示宿主环境的常规节点,例如在浏览器环境中,这就代表了一个普通的 DOM 元素,如 div、span 等
export const HostText = 6;  // 表示宿主环境的文本节点,例如在浏览器环境中,这就代表了一个文本节点

packages/react-reconciler/src/ReactFiberFlags.js:

js
export const NoFlags = 0b00000000000000000000000000; // 标识位:无
export const Placement = 0b00000000000000000000000010; // 标识位:插入
export const Update = 0b00000000000000000000000100; // 标识位:更新
export const MutationMask = Placement | Update; // 变更标识位掩码

packages/react-reconciler/src/ReactFiberClassUpdateQueue.js

js
import { markUpdateLaneFromFiberToRoot } from "./ReactFiberConcurrentUpdates";
import assign from "shared/assign";

// 定义状态更新的类型标签
export const UpdateState = 0;

/**
 * 初始化fiber节点的更新队列
 * @param {FiberNode} fiber - 需要初始化更新队列的fiber节点
 */
export function initialUpdateQueue(fiber) {
  const queue = {
    shared: {
      pending: null, // 创建一个新的更新队列,其中pending是一个循环链表
    },
  };
  fiber.updateQueue = queue;
}

/**
 * 创建一个状态更新对象
 * @returns {Update} 更新对象
 */
export function createUpdate() {
  const update = { tag: UpdateState };
  return update;
}

/**
 * 将更新对象添加到fiber节点的更新队列中
 * @param {FiberNode} fiber - 需要添加更新的fiber节点
 * @param {Update} update - 待添加的更新对象
 * @returns {FiberNode} fiber根节点
 */
export function enqueueUpdate(fiber, update) {
  const updateQueue = fiber.updateQueue;
  const pending = updateQueue.shared.pending;

  // 如果pending为空,则让update自引用形成一个循环链表
  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }

  // pending始终指向最后一个更新对象,形成一个单向循环链表
  updateQueue.shared.pending = update;

  return markUpdateLaneFromFiberToRoot(fiber);
}

/**
 * 根据老状态和更新队列中的更新计算最新的状态
 * @param {FiberNode} workInProgress - 需要计算新状态的fiber节点
 */
export function processUpdateQueue(workInProgress) {
  const queue = workInProgress.updateQueue;
  const pendingQueue = queue.shared.pending;

  // 如果有更新,则清空更新队列并开始计算新的状态
  if (pendingQueue !== null) {
    queue.shared.pending = null;
    const lastPendingUpdate = pendingQueue;
    const firstPendingUpdate = lastPendingUpdate.next;

    // 把更新链表剪开,变成一个单链表
    lastPendingUpdate.next = null;
    let newState = workInProgress.memoizedState;
    let update = firstPendingUpdate;

    // 遍历更新队列,根据老状态和更新对象计算新状态
    while (update) {
      newState = getStateFromUpdate(update, newState);
      update = update.next;
    }

    // 更新fiber节点的memoizedState
    workInProgress.memoizedState = newState;
  }
}

/**
 * 根据老状态和更新对象计算新状态
 * @param {Update} update - 更新对象
 * @param {*} prevState - 老状态
 * @returns {*} 新状态
 */
function getStateFromUpdate(update, prevState) {
  switch (update.tag) {
    case UpdateState:
      const { payload } = update;
      // 合并prevState和payload为新状态
      return assign({}, prevState, payload);
  }
}

packages/react-reconciler/src/ReactFiberConcurrentUpdates.js

js
import { HostRoot } from "./ReactWorkTags";

/**
 * 从源 Fiber 向上遍历树,找到根节点。
 * @param {Fiber} sourceFiber - 源 Fiber。
 * @returns {Node|null} - 如果找到根节点,则返回根节点;否则返回 null。
 */
export function markUpdateLaneFromFiberToRoot(sourceFiber) {
  let node = sourceFiber; 
  let parent = sourceFiber.return;
  while (parent !== null) {
    node = parent;
    parent = parent.return;
  }

  // 持续向上遍历树,直到找到根节点
  if (node.tag === HostRoot) {
    return node.stateNode;
  }

  return null;
}

shared/assign.js

js
const { assign } = Object;
export default assign;

基于 VitePress 构建