先上代码
import React from 'react';
// 创建虚拟 DOM
function createDom(fiber) {
const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type);
// 更新 DOM 属性和事件监听
updateDom(dom, {}, fiber.props);
return dom;
}
let nextUnitOfWork = null;
let wipRoot = null; // 保存对根 Fiber 的引用
let currentRoot = null; // 保存当前页面的 Fiber 树
let deletions = null;
let wipFiber = null;
let hookIndex = null;
// 提交更新到 DOM
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
// 判断是否为事件属性
const isEvent = key => key.startsWith("on");
// 判断是否为普通属性
const isProperty = key => key !== "children" && !isEvent(key);
// 判断属性是否更新
const isNew = (prev, next) => key => prev[key] !== next[key];
// 判断属性是否被移除
const isGone = (prev, next) => key => !(key in next);
// 更新 DOM 元素的属性和事件监听
function updateDom(dom, prevProps, nextProps) {
// 移除旧的或已更改的事件监听
Object.keys(prevProps)
.filter(isEvent)
.filter(
key =>
!(key in nextProps) ||
isNew(prevProps, nextProps)(key)
)
.forEach(name => {
const eventType = name
.toLowerCase()
.substring(2);
dom.removeEventListener(
eventType,
prevProps[name]
);
});
// 移除旧的属性
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach(name => {
dom[name] = "";
});
// 设置新的或更改的属性
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach(name => {
dom[name] = nextProps[name];
});
// 添加事件监听
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach(name => {
const eventType = name
.toLowerCase()
.substring(2);
dom.addEventListener(
eventType,
nextProps[name]
);
});
}
// 提交工作到 DOM
function commitWork(fiber) {
if (!fiber) {
return;
}
let domParentFiber = fiber.parent;
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.dom;
if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
domParent.appendChild(fiber.dom);
} else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectTag === "DELETION") {
// domParent.removeChild(fiber.dom);
commitDeletion(fiber, domParent);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
// 提交删除到 DOM
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child, domParent);
}
}
// 工作循环,处理 Fiber
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
// 协调子元素
function reconcileChildren(wipFiber, elements) { // 定义一个函数,接收两个参数:当前工作纤维(wipFiber)和元素数组(elements)
let index = 0; // 初始化索引变量为0,用于遍历元素数组。
let oldFiber = wipFiber.alternate && wipFiber.alternate.child; // 获取当前工作纤维的备用纤维(alternate fiber)以及其子纤维(child fiber)。
let prevSibling = null; // 初始化前一个兄弟纤维(sibling fiber)为null。
while (index < elements.length || oldFiber != null) { // 当索引小于元素数组长度或者备用纤维不为null时,执行循环。
const element = elements[index]; // 获取当前索引位置的元素。
let newFiber = null; // 初始化新的纤维为null。
const sameType = oldFiber && element && element.type == oldFiber.type; // 判断元素和备用纤维是否具有相同的类型。
if (sameType) { // 如果元素和备用纤维具有相同的类型。
newFiber = { // 创建一个新的纤维对象。
type: oldFiber.type, // 类型与备用纤维相同。
props: element.props, // 属性与元素相同。
dom: oldFiber.dom, // DOM节点与备用纤维相同。
parent: wipFiber, // 父级纤维为当前工作纤维。
alternate: oldFiber, // 备用纤维为旧的备用纤维。
effectTag: "UPDATE" // 标记为更新。
};
}
if (element && !sameType) { // 如果元素存在并且类型与备用纤维不同。
newFiber = { // 创建一个新的纤维对象。
type: element.type, // 类型与元素相同。
props: element.props, // 属性与元素相同。
dom: null, // DOM节点为null。
parent: wipFiber, // 父级纤维为当前工作纤维。
alternate: null, // 没有备用纤维。
effectTag: "PLACEMENT" // 标记为插入。
};
}
if (oldFiber && !sameType) { // 如果存在备用纤维并且类型与元素不同。
oldFiber.effectTag = "DELETION"; // 将备用纤维的标记设为删除。
deletions.push(oldFiber); // 将备用纤维添加到删除列表中。
}
if (oldFiber) { // 如果存在备用纤维。
oldFiber = oldFiber.sibling; // 获取备用纤维的兄弟纤维,准备处理下一个兄弟纤维。
}
if (index === 0) { // 如果索引为0,即处理第一个元素和对应的第一个备用纤维。
wipFiber.child = newFiber; // 将新的纤维设为当前工作纤维的子纤维。
} else if (element) { // 如果不是第一个元素,即处理后续的元素和对应的备用纤维。
prevSibling.sibling = newFiber; // 将新的纤维设为前一个兄弟纤维的兄弟纤维。
}
prevSibling = newFiber; // 将新的纤维设为前一个兄弟纤维,准备处理下一个兄弟纤维。
index++; // 索引加1,准备处理下一个元素。
}
}
// 执行工作单元
function performUnitOfWork(fiber) {
// 1. 函数组件对应的 Fiber 节点没有真实 DOM 元素
// 2. 函数组件需要运行函数获取子元素
const isFunctionComponent = fiber.type instanceof Function;
if (!isFunctionComponent && !fiber.dom) {
fiber.dom = createDom(fiber);
}
const children = isFunctionComponent ? updateFunctionComponent(fiber) : fiber.props.children;
// 第二步,为每一个新的 React 元素节点创建对应的 Fiber 节点,并判断旧的 Fiber 节点上的真实 DOM 元素是否可以复用,从而节省创建真实 DOM 元素的开销
reconcileChildren(fiber, children);
// 第三步,查找下一个工作单元
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
// 更新函数组件
function updateFunctionComponent(fiber) {
// 更新工作单元纤维
wipFiber = fiber;
// 初始化钩子索引为0
hookIndex = 0;
// 清空当前工作单元纤维的钩子数组
wipFiber.hooks = [];
// 返回更新后的纤维类型和属性
return [fiber.type(fiber.props)];
}
// 简化的 React 库
const MiniReact = {
createElement: (type, props, ...children) => {
return {
type,
props: {
...props,
children: children.map(child => {
if (typeof child === 'object') {
return child;
}
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: child,
children: [],
}
};
})
}
};
},
render: function(element, container) {
wipRoot = {
dom: container,
props: {
children: [element], // 这里的 element 是使用 MiniReact.createElement 创建的虚拟 DOM 树
},
alternate: currentRoot,
};
deletions = [];
nextUnitOfWork = wipRoot;
},
useState: function(initial) {
const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex];
const hook = {
state: oldHook ? oldHook.state : initial,
queue: [],
};
const actions = oldHook ? oldHook.queue : [];
actions.forEach(action => {
hook.state = action(hook.state);
});
const setState = action => {
hook.queue.push(action);
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot,
};
nextUnitOfWork = wipRoot;
deletions = [];
};
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
},
};
// 使用 JSX 语法
/** @jsx MiniReact.createElement */
const container = document.getElementById("root");
// 示例组件 Counter
function Counter() {
const [state, setState] = MiniReact.useState(1);
return (
<h1 onClick={() => setState(c => c + 1)}>
Count: {state}
</h1>
);
}
const element = <Counter />;
MiniReact.render(element, container);
这是一个简化的 React 实现,它实现了一个虚拟 DOM 和一些 React 的核心功能,包括元素的创建、渲染、组件的状态管理等。以下是对代码的详细解释:
- 创建虚拟 DOM(
createDom函数) :根据给定的 Fiber 节点类型创建相应的虚拟 DOM。如果类型是文本元素('TEXT_ELEMENT'),则创建一个文本节点;否则,创建一个具有相应类型的 DOM 元素,并调用updateDom函数来更新 DOM 属性。 - 更新 DOM 元素(
updateDom函数) :根据前后属性的变化,更新 DOM 元素的属性和事件监听。首先,移除不再需要的事件监听,然后移除不再需要的属性,接着设置新的或更改的属性,最后添加新的事件监听。 - 提交工作到 DOM(
commitWork函数) :在完成 Fiber 树的构建后,将生成的 Fiber 树(称为 wipRoot)的变化提交到真实的 DOM。根据 Fiber 节点的effectTag属性,执行插入(PLACEMENT)、更新(UPDATE)和删除(DELETION)等操作。 - 工作循环(
workLoop函数) :通过requestIdleCallback触发工作循环,持续执行工作直到时间片用尽。在工作循环中,执行performUnitOfWork函数来处理每一个工作单元(Fiber 节点),直至没有待处理的工作单元。 - 协调子元素(
reconcileChildren函数) :为当前工作纤维(wipFiber)和传入的元素数组协调子元素的变化。对比新旧元素,根据类型、属性等判断是否需要插入、更新或删除 DOM 元素。 - 执行工作单元(
performUnitOfWork函数) :处理一个工作单元的逻辑。它首先根据 Fiber 节点类型和是否为函数组件,执行相应的更新逻辑,然后继续处理子元素。 - 更新函数组件(
updateFunctionComponent函数) :用于更新函数组件的状态。它初始化钩子索引、清空钩子数组,并执行函数组件逻辑,获取返回的元素数组,进而协调子元素的变化。 - 简化的 React 库(
MiniReact对象) :提供了一些简化的 React API,包括创建元素、渲染、状态管理等。它使用createElement函数创建虚拟 DOM 元素,并使用render函数将虚拟 DOM 渲染到真实的 DOM。 - 使用 JSX 语法(
jsx注释和组件) :使用 JSX 语法来创建组件并渲染到指定的容器中。在示例中,创建了一个名为Counter的组件,使用MiniReact.useState来管理状态,以及使用onClick事件来更新状态并重新渲染。
这段代码演示了一个简化的 React 实现,可以帮助你更好地理解 React 的工作原理,尤其是虚拟 DOM、协调和状态管理的概念。请注意,实际的 React 实现更加复杂和优化,但这个示例可以作为入门的参考。