前言
在现代前端开发中,React 无疑是最受欢迎的框架之一。然而,许多开发者只是停留在使用层面,对其底层原理知之甚少。今天,我们将一起动手实现一个简易版的 React,重点探索其核心的 Fiber 架构和协调算法。通过这个实践,你不仅能深入理解 React 的工作原理,还能提升解决复杂前端架构问题的能力。
一、为什么需要 Fiber 架构?
在 React 16 之前,React 使用的是 Stack Reconciler(栈协调器)。这个协调器有一个致命缺陷:一旦开始渲染,就无法中断。如果组件树很大,JavaScript 会长时间占用主线程,导致用户交互卡顿。
Fiber 架构的诞生正是为了解决这个问题。它引入了以下关键特性:
- 可中断的渲染过程:将渲染工作拆分成多个小任务
- 增量渲染:可以分段完成渲染工作
- 优先级调度:不同更新拥有不同优先级
- 更好的错误处理:引入了 Error Boundaries
二、Fiber 节点的数据结构
让我们首先定义 Fiber 节点的基本结构:
class FiberNode {
constructor(type, props) {
// 节点类型
this.type = type; // 'div', 'span', 或函数组件、类组件
this.props = props;
this.key = props.key;
// 节点关系
this.return = null; // 父节点
this.child = null; // 第一个子节点
this.sibling = null; // 下一个兄弟节点
// 渲染相关
this.stateNode = null; // 对应的真实 DOM 节点
this.alternate = null; // 对应的旧 Fiber 节点
// 副作用标记
this.effectTag = null; // PLACEMENT, UPDATE, DELETION
this.firstEffect = null; // 第一个有副作用的子节点
this.lastEffect = null; // 最后一个有副作用的子节点
this.nextEffect = null; // 下一个有副作用的节点
// 状态
this.pendingProps = props;
this.memoizedProps = null;
this.memoizedState = null;
// 对于函数组件
this.memoizedState = null; // hooks 链表
// 工作单元状态
this.expirationTime = 0; // 过期时间
}
}
三、实现简易协调算法
协调算法(Reconciliation)是 React 的核心,它负责比较新旧虚拟 DOM 树的差异。让我们实现一个简化版本:
class MiniReact {
constructor() {
this.wipRoot = null; // work in progress root
this.currentRoot = null; // 当前渲染的根节点
this.nextUnitOfWork = null; // 下一个工作单元
this.deletions = []; // 需要删除的节点
}
// 创建虚拟 DOM 元素
createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === 'object'
? child
: this.createTextElement(child)
),
},
};
}
createTextElement(text) {
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: [],
},
};
}
// 渲染入口
render(element, container) {
this.wipRoot = {
dom: container,
props: {
children: [element],
},
alternate: this.currentRoot,
};
this.deletions = [];
this.nextUnitOfWork = this.wipRoot;
// 启动工作循环
requestIdleCallback(this.workLoop.bind(this));
}
// 工作循环
workLoop(deadline) {
let shouldYield = false;
while (this.nextUnitOfWork && !shouldYield) {
this.nextUnitOfWork = this.performUnitOfWork(
this.nextUnitOfWork
);
shouldYield = deadline.timeRemaining() < 1;
}
if (!this.nextUnitOfWork && this.wipRoot) {
this.commitRoot();
}
requestIdleCallback(this.workLoop.bind(this));
}
// 执行工作单元
performUnitOfWork(fiber) {
const isFunctionComponent =
fiber.type instanceof Function;
if (isFunctionComponent) {
this.updateFunctionComponent(fiber);
} else {
this.updateHostComponent(fiber);
}
// 返回下一个工作单元
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
return null;
}
// 处理函数组件
updateFunctionComponent(fiber) {
// 设置 hooks 索引
this.wipFiber = fiber;
this.hookIndex = 0;
this.wipFiber.hooks = [];
// 执行函数组件
const children = [fiber.type(fiber.props)];
this.reconcileChildren(fiber, children);
}
// 处理宿主组件(DOM 元素)
updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = this.createDom(fiber);
}
this.reconcileChildren(fiber, fiber.props.children);
}
// 协调子节点
reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while (index < elements.length || oldFiber != null) {
const element = elements[index];
let newFiber = null;
// 比较新旧节点
const sameType =
oldFiber &&
element &&
element.type === oldFiber.type;
if (sameType) {
// 类型相同,更新节点
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
return: wipFiber,
alternate: oldFiber,
effectTag: 'UPDATE',
};
}
if (element && !sameType) {
// 类型不同,创建新节点
newFiber = {
type: element.type,
props: element.props,
dom: null,
return: 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
commitRoot() {
this.deletions.forEach(this.commitWork.bind(this));
this.commitWork(this.wipRoot.child);
this.currentRoot = this.wipRoot;
this.wipRoot = null;
}
commitWork(fiber) {
if (!fiber) return;
let domParentFiber = fiber.return;
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.return;
}
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);
}
// 创建 DOM 节点
createDom(fiber) {
const dom =
fiber.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(fiber.type);
this.updateDom(dom, {}, fiber.props);
return dom;
}
// 更新 DOM 属性
updateDom(dom, prevProps, nextProps) {
// 移除旧的属性
Object.keys(prevProps)
.filter(isEvent)
.filter(
key =>
!(key in nextProps) ||
isNew(prevProps, nextProps)(key)
)
.forEach(name => {
const eventType = name
.toLowerCase