引言
在现代前端开发中,React 已经成为最流行的 UI 库之一。然而,许多开发者只是停留在 API 使用层面,对 React 内部的核心机制——特别是 Fiber 架构和协调算法——了解有限。本文将带你从零实现一个简易版的 React,深入剖析其核心原理,帮助你真正理解 React 是如何工作的。
一、React 核心架构演进
1.1 Stack Reconciler 的局限性
在 React 16 之前,React 使用的是 Stack Reconciler(栈协调器)。这个协调器使用递归的方式遍历虚拟 DOM 树,存在一个致命缺陷:一旦开始渲染,就无法中断。
// 旧版递归渲染(伪代码)
function render(element, container) {
// 深度优先遍历整个树
const dom = createDOM(element);
container.appendChild(dom);
if (element.children) {
element.children.forEach(child => {
render(child, dom); // 递归调用,无法中断
});
}
}
这种模式在大型应用或复杂组件树中会导致主线程长时间被占用,造成页面卡顿,用户体验下降。
1.2 Fiber 架构的革命
React 16 引入的 Fiber 架构解决了这个问题。Fiber 的核心思想是:
- 增量渲染:将渲染工作拆分成多个小任务
- 可中断、可恢复:利用浏览器的空闲时间执行任务
- 优先级调度:不同更新有不同的优先级
二、实现简易版 React 核心
2.1 定义基础数据结构
首先,我们需要定义几个核心的数据结构:
// 1. 虚拟 DOM 元素
class Element {
constructor(type, props) {
this.type = type;
this.props = props;
this.key = props.key || null;
}
}
// 2. Fiber 节点
class FiberNode {
constructor(type, props) {
// 节点基本信息
this.type = type; // 组件类型(div、span、函数组件等)
this.props = props; // 属性
this.key = props.key; // key 值
// 节点关系
this.return = null; // 父节点
this.child = null; // 第一个子节点
this.sibling = null; // 兄弟节点
// 渲染状态
this.stateNode = null; // 对应的真实 DOM 节点
this.alternate = null; // 上一次渲染的 Fiber 节点(用于 diff)
// 副作用标记
this.effectTag = null; // 更新类型(PLACEMENT、UPDATE、DELETION)
this.firstEffect = null; // 第一个有副作用的子节点
this.lastEffect = null; // 最后一个有副作用的子节点
}
}
// 3. 更新类型常量
const PLACEMENT = 'PLACEMENT'; // 新增节点
const UPDATE = 'UPDATE'; // 更新节点
const DELETION = 'DELETION'; // 删除节点
2.2 实现 createElement 函数
这是 JSX 编译后的产物,类似于 React.createElement:
function createElement(type, config, ...children) {
const props = {};
// 处理属性
if (config) {
for (let propName in config) {
if (config.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
// 处理 children
props.children = children.map(child => {
// 处理文本节点
if (typeof child === 'string' || typeof child === 'number') {
return createTextElement(child);
}
return child;
}).filter(child => child != null);
return new Element(type, props);
}
function createTextElement(text) {
return new Element('TEXT_ELEMENT', {
nodeValue: text,
children: []
});
}
三、Fiber 协调算法实现
3.1 工作循环(Work Loop)
这是 Fiber 架构的核心,实现了可中断的渲染:
let nextUnitOfWork = null; // 下一个工作单元
let wipRoot = null; // 正在构建的 Fiber 树根节点
let currentRoot = null; // 上一次渲染的 Fiber 树
let deletions = null; // 需要删除的节点列表
// 开始渲染
function render(element, container) {
wipRoot = new FiberNode('ROOT', { children: [element] });
wipRoot.stateNode = container;
deletions = [];
nextUnitOfWork = wipRoot;
// 启动工作循环
requestIdleCallback(workLoop);
}
// 工作循环
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
if (nextUnitOfWork || wipRoot) {
requestIdleCallback(workLoop);
}
}
// 执行单个工作单元
function performUnitOfWork(fiber) {
// 1. 创建当前节点的 DOM(如果有)
if (!fiber.stateNode && fiber.type !== 'TEXT_ELEMENT') {
fiber.stateNode = createDOM(fiber);
}
// 2. 协调子节点
reconcileChildren(fiber);
// 3. 返回下一个工作单元
// 深度优先遍历:child -> sibling -> return
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
return null;
}
3.2 协调子节点(Reconciliation)
这是 React 的 diff 算法核心:
function reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
elements = elements || wipFiber.props.children;
while (index < elements.length || oldFiber != null) {
const element = elements[index];
let newFiber = null;
// 比较新旧节点
const sameType = oldFiber && element && element.type === oldFiber.type;
if (sameType) {
// 类型相同,更新节点
newFiber = new FiberNode(
oldFiber.type,
element.props
);
newFiber.stateNode = oldFiber.stateNode;
newFiber.alternate = oldFiber;
newFiber.effectTag = UPDATE;
}
if (element && !sameType) {
// 类型不同,创建新节点
newFiber = new FiberNode(
element.type,
element.props
);
newFiber.effectTag = PLACEMENT;
}
if (oldFiber && !sameType) {
// 删除旧节点
oldFiber.effectTag = DELETION;
deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
// 构建 Fiber 树结构
if (index === 0) {
wipFiber.child = newFiber;
} else if (element) {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
}
3.3 提交阶段(Commit Phase)
协调完成后,一次性更新 DOM:
function commitRoot() {
// 处理删除的节点
deletions.forEach(commitWork);
// 提交所有变更
commitWork(wipRoot.child);
// 保存当前 Fiber 树
currentRoot = wipRoot;
wipRoot = null;
}
function commitWork(fiber) {
if (!fiber) return;
let domParentFiber = fiber.return;
while (!domParentFiber.stateNode) {
domParentFiber = domParentFiber.return;
}
const domParent = domParentFiber.stateNode;
if (fiber.effectTag === PLACEMENT && fiber.stateNode != null) {
domParent.appendChild(fiber.stateNode);
} else if (fiber.effectTag === UPDATE && fiber.stateNode != null) {
updateDOM(
fiber.stateNode,
fiber.alternate.props,
fiber.props
);
} else if (fiber.effectTag === DELETION) {
commitDeletion(fiber, domParent);
}
// 递归提交子