04.createRoot
同学们好,前面我们说本章的目标是实现初始化渲染,具体而言,这个初始化渲染过程完成后,可以在页面上渲染一个带有一定样式的字符串:
index.html:
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
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:
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:
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。
// 导入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:
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:
export const NoFlags = 0b00000000000000000000000000; // 标识位:无
export const Placement = 0b00000000000000000000000010; // 标识位:插入
export const Update = 0b00000000000000000000000100; // 标识位:更新
export const MutationMask = Placement | Update; // 变更标识位掩码packages/react-reconciler/src/ReactFiberClassUpdateQueue.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
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
const { assign } = Object;
export default assign;