前言
在现代前端开发中,React 已经成为最流行的 UI 库之一。然而,许多开发者只是停留在使用层面,对其底层原理知之甚少。今天,我们将一起动手实现一个简易版的 React,重点解析其核心的 Fiber 架构和协调算法。通过这个实践,你不仅能深入理解 React 的工作原理,还能掌握如何构建自己的虚拟 DOM 系统。
一、React 核心概念回顾
在开始编码之前,我们先简要回顾一下 React 的几个核心概念:
- 虚拟 DOM:JavaScript 对象表示的 DOM 树,用于与实际 DOM 进行对比
- 协调(Reconciliation):React 比较新旧虚拟 DOM 并找出最小更新操作的过程
- Fiber 架构:React 16 引入的新架构,支持增量渲染和更好的调度能力
二、实现基础虚拟 DOM
首先,让我们定义虚拟 DOM 的数据结构:
// 虚拟 DOM 元素类型
const ELEMENT_TYPES = {
TEXT_ELEMENT: 'TEXT_ELEMENT',
HOST_ELEMENT: 'HOST_ELEMENT',
}
// 创建文本元素
function createTextElement(text) {
return {
type: ELEMENT_TYPES.TEXT_ELEMENT,
props: {
nodeValue: text,
children: [],
},
}
}
// 创建元素(类似 React.createElement)
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === 'object'
? child
: createTextElement(child)
),
},
}
}
三、实现 Fiber 数据结构
Fiber 是 React 协调算法的核心数据结构,它是一个工作单元,包含了组件状态、副作用等信息:
// Fiber 节点结构
class FiberNode {
constructor(type, props) {
// 标识
this.type = type
this.key = props.key
this.elementType = null
// 链表结构
this.return = null // 父节点
this.child = null // 第一个子节点
this.sibling = null // 兄弟节点
// 状态
this.pendingProps = props
this.memoizedProps = null
this.memoizedState = null
this.updateQueue = null
// 副作用
this.effectTag = null
this.firstEffect = null
this.lastEffect = null
// 备用树(用于双缓存)
this.alternate = null
// 渲染相关
this.stateNode = null // 对应的真实 DOM 节点
}
}
// 工作循环状态
let nextUnitOfWork = null
let wipRoot = null // work in progress root
let currentRoot = null // 当前渲染的根
let deletions = null // 需要删除的节点列表
四、实现协调算法(Reconciliation)
协调算法是 React 最核心的部分,它决定了如何高效地更新 DOM:
// 协调子节点
function 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
// 比较新旧 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 (index === 0) {
wipFiber.child = newFiber
} else if (element) {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
}
}
五、实现 Fiber 工作循环
Fiber 架构的关键是增量渲染,我们将工作分解成小单元:
// 执行工作单元
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 workLoop(deadline) {
let shouldYield = false
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1
}
if (!nextUnitOfWork && wipRoot) {
commitRoot()
}
requestIdleCallback(workLoop)
}
// 启动渲染
function render(element, container) {
wipRoot = {
stateNode: container,
props: {
children: [element],
},
alternate: currentRoot,
}
deletions = []
nextUnitOfWork = wipRoot
requestIdleCallback(workLoop)
}
六、实现提交阶段(Commit Phase)
协调阶段完成后,我们需要将变更提交到真实 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)
}
// 创建 DOM 节点
function createDOM(fiber) {
const dom =
fiber.type === ELEMENT_TYPES.TEXT_ELEMENT
? document.createTextNode('')
: document.createElement(fiber.type)
updateDOM(dom, {}, fiber.props)
return dom
}
// 更新 DOM 属性
function updateDOM(dom, prevProps, nextProps) {
// 移除旧的属性
Object.keys(prevProps)
.filter(isProperty)
.filter(key => !(key in nextProps))
.forEach(name => {
dom[name] = ''
})
// 设置新的属性
Object.keys(nextProps)
.filter(isProperty)
.filter(key => prevProps[key] !== nextProps[key])
.forEach(name => {
dom[name] = nextProps[name]
})
// 处理事件监听器
Object.keys(prevProps)
.filter(isEvent)
.filter(key => !(key in nextProps) || prevProps[key] !== nextProps[key])
.forEach(name => {
const eventType = name.toLowerCase().substring(2)
dom.removeEventListener(eventType, prevProps[name])
})
Object.keys(nextProps)
.filter(isEvent)
.filter(key => prevProps[key] !== nextProps[key])
.forEach(name => {
const eventType = name.toLowerCase().substring(2)
dom.addEventListener(eventType, nextProps[name])
})
}
七、实现函数组件支持
让我们添加对函数组件的支持:
// 处理函数组件
function