引言
在现代前端开发中,React 已经成为了最流行的 UI 库之一。然而,许多开发者只是停留在使用层面,对其底层原理知之甚少。本文将带你从零实现一个简易版的 React,重点解析 React 的核心机制——Fiber 架构和协调算法。通过这个实践过程,你不仅能深入理解 React 的工作原理,还能提升对现代前端框架设计的认识。
一、React 核心架构演进
1.1 Stack Reconciler 的局限性
在 React 16 之前,React 使用的是 Stack Reconciler(栈协调器)。这个协调器使用递归的方式遍历组件树,存在一个致命缺陷:一旦开始渲染,就必须完成整个树的遍历,无法中断。这会导致主线程被长时间占用,造成页面卡顿。
// 传统的递归渲染(简化版)
function render(element, container) {
// 递归处理子元素
element.children?.forEach(child => {
render(child, container);
});
// 渲染当前元素
mount(element, container);
}
1.2 Fiber 架构的革命
React 16 引入了 Fiber 架构,这是一个完全重写的协调引擎。Fiber 的核心思想是将渲染工作拆分成多个小单元,每个单元可以在浏览器空闲时执行,实现了可中断的渲染过程。
二、实现简易版 React 核心
2.1 定义 Fiber 节点结构
首先,我们需要定义 Fiber 节点的数据结构:
class FiberNode {
constructor(type, props) {
// 节点类型('div', 'span' 或函数组件等)
this.type = type;
// 节点属性
this.props = props;
// DOM 节点
this.stateNode = null;
// 父节点
this.return = null;
// 第一个子节点
this.child = null;
// 下一个兄弟节点
this.sibling = null;
// 备用节点(用于 diff 比较)
this.alternate = null;
// 副作用标签
this.effectTag = null;
// 第一个副作用节点
this.firstEffect = null;
// 下一个副作用节点
this.nextEffect = null;
}
}
2.2 实现虚拟 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: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: [],
},
};
}
三、Fiber 协调算法详解
3.1 双缓存技术
React 使用双缓存技术来优化更新过程。每个 Fiber 节点都有一个 alternate 属性,指向上一次渲染的 Fiber 节点。
// 创建 workInProgress 树的根节点
function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = new FiberNode(current.type, pendingProps);
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.props = pendingProps;
workInProgress.effectTag = null;
workInProgress.firstEffect = null;
workInProgress.nextEffect = null;
}
workInProgress.child = current.child;
workInProgress.sibling = current.sibling;
workInProgress.stateNode = current.stateNode;
return workInProgress;
}
3.2 协调过程实现
协调过程分为两个阶段:render 阶段和 commit 阶段。
let nextUnitOfWork = null;
let wipRoot = null;
let currentRoot = null;
let deletions = null;
function render(element, container) {
wipRoot = {
stateNode: container,
props: {
children: [element],
},
alternate: currentRoot,
};
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();
}
requestIdleCallback(workLoop);
}
3.3 执行单元工作
function performUnitOfWork(fiber) {
// 1. 创建 DOM 节点(如果需要)
if (!fiber.stateNode) {
fiber.stateNode = createDOM(fiber);
}
// 2. 协调子元素
const elements = fiber.props.children;
reconcileChildren(fiber, elements);
// 3. 返回下一个工作单元
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
return null;
}
function createDOM(fiber) {
const dom =
fiber.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(fiber.type);
updateDOM(dom, {}, fiber.props);
return dom;
}
3.4 子节点协调算法
这是 React 协调算法的核心部分:
function reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate?.child;
let prevSibling = null;
while (index < elements.length || oldFiber != null) {
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,
stateNode: oldFiber.stateNode,
return: wipFiber,
alternate: oldFiber,
effectTag: 'UPDATE',
};
}
if (element && !sameType) {
// 类型不同,创建新节点
newFiber = {
type: element.type,
props: element.props,
stateNode: null,
return: wipFiber,
alternate: null,
effectTag: 'PLACEMENT',
};
}
if (oldFiber && !sameType) {
// 删除旧节点
oldFiber.effectTag = 'DELETION';
deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (newFiber) {
if (index === 0) {
wipFiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
}
index++;
}
}
四、提交阶段实现
4.1 批量更新 DOM
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
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);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function commitDeletion(fiber, domParent) {
if (fiber.stateNode) {
domParent.removeChild(fiber.stateNode);
} else {
commitDeletion(fiber.child, domParent);
}
}
4.2 属性更新
function updateDOM(dom, prevProps, nextProps) {
const isEvent = key => key.startsWith('on');
const isProperty = key => key