组件的渲染过程本质就是把这种类型的 vnode 渲染成真实 DOM
更新组件主要做了三件事:
- 更新组件的 vnode 节点
- 渲染新的子树 vnode
- 根据新旧 vnode 执行 patch 阶段
核心 patch 逻辑:
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
// 如果存在新旧节点, 且新旧节点类型不同,则销毁旧节点
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
// n1 设置为 null 保证后续都走 mount 逻辑
n1 = null
}
const { type, shapeFlag } = n2
switch (type) {
case Text:
// 处理文本节点
break
case Comment:
// 处理注释节点
break
case Static:
// 处理静态节点
break
case Fragment:
// 处理 Fragment 元素
break
default:
if (shapeFlag & 1 /* ELEMENT */) {
// 处理普通 DOM 元素
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
}
else if (shapeFlag & 6 /* COMPONENT */) {
// 处理组件
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
}
else if (shapeFlag & 64 /* TELEPORT */) {
// 处理 TELEPORT
}
else if (shapeFlag & 128 /* SUSPENSE */) {
// 处理 SUSPENSE
}
}
}
function isSameVNodeType (n1, n2) {
// n1 和 n2 节点的 type 和 key 都相同,才是相同节点
return n1.type === n2.type && n1.key === n2.key
}
vue 的核心 patch 逻辑主要分为以下几种情况:
- 组件
- 普通元素
- 纯文本
- vnode 数组
- 空
对于一个元素的子节点 vnode 可能会有三种情况:纯文本、vnode 数组和空。那么根据排列组合对于新旧子节点来说就有九种情况:
- 旧子节点是纯文本:
- 新子节点是纯文本:替换新文本
- 新子节点是空:删除旧子节点
- 新子节点是 vnode 数组:清空文本,添加多个子节点
- 旧子节点是空:
- 新子节点是纯文本:添加新文本节点
- 新子节点是空:什么都不做
- 新子节点是 vnode 数组:添加多个新子节点
- 旧子节点是 vnode 数组:
- 新子节点是纯文本:删除旧字节点,添加新文本节点
- 新子节点是空:删除旧子节点
- 新子节点是 vnode 数组:完整 diff 子节点(核心 diff 算法)