前言
在当今前端开发领域,React 无疑是最受欢迎的 UI 库之一。然而,对于许多开发者来说,React 的内部工作机制仍然像一个"黑盒子"。本文将带你从零开始,实现一个极简版的 React,重点解析其核心的 Fiber 架构和协调算法。通过这个实践过程,你不仅能深入理解 React 的工作原理,还能掌握现代前端框架设计的核心思想。
一、React 核心架构演进
在深入代码实现之前,让我们先了解一下 React 架构的演进历程:
1.1 Stack Reconciler(React 15 及之前)
传统的协调算法使用递归方式遍历虚拟 DOM 树,这个过程是同步且不可中断的。当组件树很大时,会导致主线程被长时间占用,造成页面卡顿。
1.2 Fiber Reconciler(React 16+)
Fiber 架构引入了可中断的异步渲染机制,将渲染工作分割成多个小任务单元(Fiber 节点),允许浏览器在任务之间处理用户交互,从而实现更流畅的用户体验。
二、实现极简版 React 的核心模块
2.1 虚拟 DOM 结构
首先,我们定义虚拟 DOM 的基本结构:
// 虚拟 DOM 元素类型
const ElementTypes = {
TEXT_ELEMENT: 'TEXT_ELEMENT',
HOST_COMPONENT: 'HOST_COMPONENT',
CLASS_COMPONENT: 'CLASS_COMPONENT',
FUNCTION_COMPONENT: 'FUNCTION_COMPONENT'
};
// 创建虚拟 DOM 元素的工厂函数
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === 'object'
? child
: createTextElement(child)
)
}
};
}
// 创建文本元素
function createTextElement(text) {
return {
type: ElementTypes.TEXT_ELEMENT,
props: {
nodeValue: text,
children: []
}
};
}
2.2 Fiber 节点结构
Fiber 是 React 的最小工作单元,每个 Fiber 节点对应一个组件或 DOM 元素:
class FiberNode {
constructor(type, props) {
// 节点标识
this.type = type;
this.props = props;
this.key = props.key;
// 节点关系
this.parent = null;
this.child = null;
this.sibling = null;
this.alternate = null; // 指向旧的 Fiber 节点
// 节点状态
this.stateNode = null; // 对应的真实 DOM 或组件实例
this.pendingProps = props;
this.memoizedProps = null;
this.memoizedState = null;
// 副作用标记
this.effectTag = null;
this.firstEffect = null;
this.lastEffect = null;
this.nextEffect = null;
// 工作单元状态
this.expirationTime = 0;
this.updateQueue = null;
}
}
2.3 协调算法核心实现
协调算法(Reconciliation)是 React 最核心的部分,它负责比较新旧虚拟 DOM 树的差异:
class Reconciler {
constructor() {
this.nextUnitOfWork = null;
this.currentRoot = null;
this.wipRoot = null;
this.deletions = [];
}
// 开始渲染
render(element, container) {
this.wipRoot = {
dom: container,
props: {
children: [element]
},
alternate: this.currentRoot
};
this.deletions = [];
this.nextUnitOfWork = this.wipRoot;
// 启动工作循环
this.workLoop();
}
// 工作循环 - 模拟 React 的调度机制
workLoop() {
while (this.nextUnitOfWork) {
this.nextUnitOfWork = this.performUnitOfWork(this.nextUnitOfWork);
}
if (!this.nextUnitOfWork && this.wipRoot) {
this.commitRoot();
}
}
// 执行单个工作单元
performUnitOfWork(fiber) {
// 1. 创建或更新 DOM
if (!fiber.dom) {
fiber.dom = this.createDom(fiber);
}
// 2. 协调子元素
const elements = fiber.props.children;
this.reconcileChildren(fiber, elements);
// 3. 返回下一个工作单元
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
return null;
}
// 协调子节点
reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while (index < elements.length || oldFiber) {
const element = elements[index];
let newFiber = null;
// 比较新旧 Fiber
const sameType = oldFiber && element && element.type === oldFiber.type;
if (sameType) {
// 类型相同,更新属性
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: 'UPDATE'
};
}
if (element && !sameType) {
// 类型不同,创建新节点
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: 'PLACEMENT'
};
}
if (oldFiber && !sameType) {
// 删除旧节点
oldFiber.effectTag = 'DELETION';
this.deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (newFiber) {
if (index === 0) {
wipFiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
}
index++;
}
}
// 创建真实 DOM
createDom(fiber) {
const dom =
fiber.type === ElementTypes.TEXT_ELEMENT
? document.createTextNode('')
: document.createElement(fiber.type);
this.updateDom(dom, {}, fiber.props);
return dom;
}
// 更新 DOM 属性
updateDom(dom, prevProps, nextProps) {
// 移除旧的属性
Object.keys(prevProps)
.filter(key => key !== 'children')
.forEach(name => {
if (name.startsWith('on')) {
const eventType = name.toLowerCase().substring(2);
dom.removeEventListener(eventType, prevProps[name]);
} else {
dom[name] = '';
}
});
// 添加新的属性
Object.keys(nextProps)
.filter(key => key !== 'children')
.forEach(name => {
if (name.startsWith('on')) {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, nextProps[name]);
} else {
dom[name] = nextProps[name];
}
});
}
// 提交变更到 DOM
commitRoot() {
this.deletions.forEach(this.commitWork);
this.commitWork(this.wipRoot.child);
this.currentRoot = this.wipRoot;
this.wipRoot = null;
}
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) {
this.updateDom(
fiber.dom,
fiber.alternate.props,
fiber.props
);
} else if (fiber.effectTag === 'DELETION') {
this.commitDeletion(fiber, domParent);
}
this.commitWork(fiber.child);
this.commitWork(fiber.sibling);
}
commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom);
} else {
this.commitDeletion(fiber.child, dom