深入浅出:手把手实现Mini-React中的Props更新机制
在实现React-like框架时,props的高效更新是核心挑战之一。本文将带您逐步实现Mini-React中的props更新机制,通过菜市场摊位更新的生动比喻,让复杂概念变得通俗易懂。
一、获取新的DOM树
更新时需要获取新的DOM树结构,类似初始化时的render过程。我们通过currentRoot
记录当前根节点,更新时将其作为起点:
tsx
let currentRoot = null
function commitRoot() {
commitWork(root.child)
currentRoot = root // 更新后记录为当前根节点
root = null
}
function update() {
nextWorkOfUnit = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot // 关键:建立新旧节点联系
}
root = nextWorkOfUnit
}
二、建立新旧节点对比
通过alternate
指针连接新旧节点,实现差异更新。就像菜市场老板对比新旧进货单:
tsx
function initChildren(fiber, children) {
let oldFiber = fiber.alternate?.child // 昨天的进货单
let prevChild = null
children.forEach((child, index) => {
const isSameType = oldFiber?.type === child.type
const newFiber = isSameType
? { // 同类型蔬菜:更新价格牌
type: child.type,
props: child.props,
dom: oldFiber.dom,
effectTag: "update",
alternate: oldFiber
}
: { // 新品种蔬菜:准备新位置牌
type: child.type,
props: child.props,
dom: null,
effectTag: "placement"
}
if (oldFiber) oldFiber = oldFiber.sibling // 查看下个旧节点
// 构建链表关系
if (index === 0) fiber.child = newFiber
else prevChild.sibling = newFiber
prevChild = newFiber
})
}
三、提交阶段执行DOM操作
在commit阶段根据effectTag
执行具体操作,类似老板布置摊位:
tsx
function commitWork(fiber) {
if (!fiber) return
// 向上找到真实DOM父节点(跳过函数组件)
let parentFiber = fiber.parent
while (!parentFiber.dom) {
parentFiber = parentFiber.parent
}
// 根据标签执行操作
if (fiber.effectTag === "update") {
updateProps(fiber.dom, fiber.props, fiber.alternate?.props)
} else if (fiber.effectTag === "placement" && fiber.dom) {
parentFiber.dom.append(fiber.dom) // 挂新位置牌
}
// 深度优先遍历子节点和兄弟节点
commitWork(fiber.child)
commitWork(fiber.sibling)
}
四、智能属性更新
属性更新需要特殊处理事件监听器,避免重复绑定:
tsx
function updateProps(dom, nextProps, prevProps = {}) {
// 移除旧属性(昨天有今天没有)
Object.keys(prevProps).forEach(key => {
if (key !== "children" && !(key in nextProps)) {
dom.removeAttribute(key)
}
})
// 设置新属性(新增或修改)
Object.keys(nextProps).forEach(key => {
if (key === "children") return
if (nextProps[key] !== prevProps[key]) {
if (key.startsWith("on")) { // 事件处理
const eventType = key.slice(2).toLowerCase()
dom.removeEventListener(eventType, prevProps[key]) // 关键!
dom.addEventListener(eventType, nextProps[key])
} else {
dom[key] = nextProps[key] // 普通属性
}
}
})
}
五、手动更新测试
通过手动触发更新验证机制:
tsx
function Counter() {
const [count, setCount] = useState(10)
function handleClick() {
setCount(count + 1) // 实际应触发更新
React.update() // 手动更新演示
}
return (
<div>
Count: {count}
<button onClick={handleClick}>+1</button>
</div>
)
}