各位前端大佬们,今天将分享一个简化版React的核心实现,包含虚拟DOM、Fiber架构、协调算法等关键模块。通过200行代码,我们将理解React如何实现高效渲染。当然也是在网上找视频跟着学习的,自己跟着手敲了一遍。
核心架构流程
rust
jsx -> babel/swc转换 -> createElement生成虚拟DOM -> Fiber架构处理 -> Diff算法 -> DOM提交
核心模块解析
1. 虚拟DOM构建
javascript
const React = {
createElement(type, props, ...children) {
// 将子节点包装为TEXT_ELEMENT类型
return { type, props: { ...props, children } }
},
createTextElement(text) {
return { type: "TEXT_ELEMENT", props: { nodeValue: text } }
}
}
- 作用:将JSX转换为虚拟DOM对象
- 特点 :
- 递归处理子节点
- 文本节点特殊标记为
TEXT_ELEMENT
2. Fiber架构核心
关键变量:
javascript
let nextUnitOfWork = null // 当前处理的Fiber节点
let wipRoot = null // 当前Fiber树根节点
let currentRoot = null // 前次渲染的Fiber树
let deletions = [] // 待删除节点集合
工作循环:
javascript
function workLoop(deadline) {
while (nextUnitOfWork && 剩余时间>1ms) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
if (!nextUnitOfWork && wipRoot) commitRoot()
requestIdleCallback(workLoop)
}
- 时间切片 :通过
requestIdleCallback
在浏览器空闲时段执行任务 - 可中断渲染:每次处理单个Fiber节点后检查剩余时间
performUnitOfWork与reconcileChildren协作流程解析
Fiber节点处理流水线
1. performUnitOfWork核心流程
javascript
function performUnitOfWork(fiber) {
// 步骤1:创建当前节点的DOM(若不存在)
if (!fiber.dom) fiber.dom = createDom(fiber);
// 步骤2:协调子节点(核心Diff算法)
reconcileChildren(fiber, fiber.props.children);
// 步骤3:返回下一个工作单元
if (fiber.child) return fiber.child; // 优先处理子节点
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) return nextFiber.sibling; // 其次处理兄弟节点
nextFiber = nextFiber.parent; // 最后回溯父节点
}
}
执行逻辑示意图:
php
父Fiber
↓ (child)
子Fiber → (sibling) → 兄弟Fiber
↓ (child)
孙Fiber
2. reconcileChildren工作原理
javascript
function reconcileChildren(fiber, elements) {
let index = 0;
let oldFiber = fiber.alternate?.child; // 获取旧Fiber子节点
let prevSibling = null;
while (index < elements.length || oldFiber) {
const element = elements[index];
// 新旧节点对比决策树
const sameType = oldFiber && element?.type === oldFiber.type;
// 生成新Fiber的三种情况
if (sameType) { /* 更新逻辑 */ }
if (!sameType && element) { /* 新建逻辑 */ }
if (oldFiber && !sameType) { /* 删除逻辑 */ }
// 构建链表结构
if (index === 0) fiber.child = newFiber;
else prevSibling.sibling = newFiber;
prevSibling = newFiber;
index++;
}
}
协作流程图解:
bash
───────────────────────────────────────
performUnitOfWork
│
├─ 1. 创建DOM(如需)
│
└─ 2. 调用reconcileChildren
│
├─ 对比新旧子节点
├─ 生成带effectTag的Fiber
└─ 构建child/sibling链表
───────────────────────────────────────
关键协作机制详解
1. 深度优先遍历
- 子节点优先:总是先处理当前Fiber的子节点
- 链表回溯:当子节点处理完成后,通过sibling指针横向移动
- 完成条件:当没有子节点和兄弟节点时,结束当前分支
2. 增量构建策略
javascript
// 示例:处理<div>包含两个<span>的情况
父Fiber (div)
↓ child
子Fiber1 (span1)
↓ sibling
子Fiber2 (span2)
reconcileChildren
为每个子元素创建Fiber节点- 通过
child
和sibling
指针形成链表结构 performUnitOfWork
按深度优先顺序遍历链表
3. 操作类型标记
在协调过程中会为每个Fiber节点打标:
- PLACEMENT:需要插入DOM的新节点
- UPDATE:需要属性更新的已有节点
- DELETION:需要移除的旧节点
完整协作示例
假设有如下更新操作:
javascript
// 初始渲染
<div>
<span>Hello</span>
</div>
// 更新后
<div>
<p>World</p>
</div>
执行步骤:
performUnitOfWork
处理div FiberreconcileChildren
对比新旧span和p- 标记span为DELETION
- 创建p的PLACEMENT Fiber
- 提交阶段先删除span节点
- 插入新的p节点
整个过程通过requestIdleCallback
分片执行,实现无阻塞渲染。
ini
### 3. 协调算法(Diff)
```javascript
function reconcileChildren(fiber, elements) {
let oldFiber = fiber.alternate?.child
while (元素未遍历完 || 旧Fiber存在) {
const sameType = oldFiber与当前元素类型相同
if (sameType) {
// 更新节点
newFiber = { ...复用旧DOM, effectType: "UPDATE" }
} else if (新元素存在) {
// 新建节点
newFiber = createFiber(element)
} else if (旧Fiber存在) {
// 删除节点
oldFiber.effectType = "DELETION"
deletions.push(oldFiber)
}
// 构建链表结构
if (是第一个子节点) fiber.child = newFiber
else preSibling.sibling = newFiber
}
}
- 三种操作类型 :
- UPDATE:类型相同,更新属性
- PLACEMENT:新增节点
- DELETION:移除旧节点
4. DOM提交阶段
javascript
function commitRoot() {
deletions.forEach(commitWork) // 先处理删除
commitWork(wipRoot.child) // 递归提交变更
}
function commitWork(fiber) {
if (fiber.effectType === "PLACEMENT") {
parent.dom.appendChild(fiber.dom)
} else if (fiber.effectType === "UPDATE") {
updateDom(fiber.dom, oldProps, newProps)
} else if (fiber.effectType === "DELETION") {
parent.dom.removeChild(fiber.dom)
}
// 深度优先遍历
commitWork(fiber.child)
commitWork(fiber.sibling)
}
- 批量处理:先处理所有删除操作,再递归提交其他变更
- DOM操作:最终将Fiber树映射到真实DOM
完整代码实现
javascript
// React整体架构
// jsx => babel|swc => React.createElement => ReactDom.render => FIber => diff算法 => commit阶段
const React = {
createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
if (typeof child === "object") {
return child;
}
return React.createTextElement(child);
}),
},
};
},
createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
},
};
// 实现虚拟dom转fiber结构和时间切片
let nextUnitOfWork = null;
let wipRoot = null;
let currentRoot = null;
let deletions = null;
function render(element, container) {
wipRoot = {
dom: container,
props: { children: [element] },
alternate: currentRoot,
};
deletions = [];
nextUnitOfWork = wipRoot;
}
function createDom(fiber) {
const dom = fiber.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(fiber.type);
updateDom(dom, {}, fiber.props);
return dom;
}
function updateDom(dom, preProps, nextProps) {
Object.keys(preProps).forEach(name => {
if (name !== "children") dom[name] = "";
});
Object.keys(nextProps).forEach(name => {
if (name !== "children") dom[name] = nextProps[name];
});
}
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 performUnitOfWork(fiber) {
if (!fiber.dom) fiber.dom = createDom(fiber);
reconcileChildren(fiber, fiber.props.children);
if (fiber.child) return fiber.child;
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent;
}
}
function reconcileChildren(fiber, elements) {
let index = 0;
let oldFiber = fiber.alternate?.child;
while (index < elements.length || oldFiber) {
const element = elements[index];
let newFiber = null;
const sameType = oldFiber && element?.type === oldFiber.type;
if (sameType) {
newFiber = { ...oldFiber, props: element.props, effectType: "UPDATE" };
}
if (!sameType && element) {
newFiber = { type: element.type, props: element.props, effectType: "PLACEMENT" };
}
if (oldFiber && !sameType) {
oldFiber.effectType = "DELETION";
deletions.push(oldFiber);
}
// 构建链表结构...
index++;
}
}
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
function commitWork(fiber) {
if (!fiber) return;
const domParent = fiber.parent.dom;
if (fiber.effectType === "PLACEMENT" && fiber.dom) {
domParent.appendChild(fiber.dom);
} else if (fiber.effectType === "UPDATE") {
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectType === "DELETION") {
domParent.removeChild(fiber.dom);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
// 测试用例
render(
React.createElement("div", { id: "box" },
React.createElement("span", null, "hello")
),
document.getElementById("root")
);
总结
本实现核心展示了:
- 虚拟DOM构建:通过createElement创建轻量级JS对象
- Fiber架构:将渲染任务拆分为可中断的工作单元
- 协调算法:通过类型比较实现高效DOM更新
- 提交阶段:批量处理DOM操作保证性能
完整呈现了React核心渲染机制。建议各位大佬可以自己实现一次,以便于有时间可以拿出来回顾回顾,达到充分理解的程度。