8.11 Fragment
**Fragment(片段)**是 Vue.js 3 新增的 vnode 类型,用来描述拥有多个根节点的模板;它自身不渲染任何真实 DOM,只渲染它的 children。
一、为什么需要 Fragment
模板里可能出现多个并列的根节点:
<li>1</li>
<li>2</li>
<li>3</li>
片段没有所谓的标签名称,因此也需要为它创建唯一标识;而它的 children 存储的就是模板中所有根节点。
const Fragment = Symbol()
const vnode = {
type: Fragment,
children: [
{ type: 'li', children: '1' },
{ type: 'li', children: '2' },
{ type: 'li', children: '3' }
]
}
二、patch 中处理 Fragment
渲染 Fragment 与渲染普通元素的区别在于:Fragment 本身并不渲染任何内容,所以只需要处理它的子节点。
} else if (type === Fragment) {
// 处理 Fragment 类型的 vnode
if (!n1) {
// 旧 vnode 不存在 → 将 Fragment 的 children 逐个挂载
n2.children.forEach(c => patch(null, c, container))
} else {
// 旧 vnode 存在 → 只需要更新 Fragment 的 children
patchChildren(n1, n2, container)
}
}
三、卸载 Fragment
Fragment 本身不会渲染任何真实 DOM,因此不能按常规方式取 vnode.el 再 removeChild;卸载时要遍历它的 children 数组,将其中的节点逐个卸载:
function unmount(vnode) {
// 卸载的 vnode 类型为 Fragment → 卸载其 children
if (vnode.type === Fragment) {
vnode.children.forEach(c => unmount(c))
return
}
const parent = vnode.el.parentNode
if (parent) {
parent.removeChild(vnode.el)
}
}
这正是把卸载封装进
unmount的价值:有了统一入口,才有机会在卸载时检测 vnode 的类型并做特殊处理(同理可处理组件类型)。
四、渲染效果
const vnode = {
type: 'ul',
children: [
{
type: Fragment,
children: [
{ type: 'li', children: '1' },
{ type: 'li', children: '2' },
{ type: 'li', children: '3' }
]
}
]
}
renderer.render(vnode, document.querySelector('#app'))
结果是三个 li 直接挂在 ul 下,中间不会多出任何包裹元素 —— 片段本身是「透明」的。
相关
- 8.10 文本节点和注释节点:同样用 Symbol 作为类型标识的节点
- 8.9 更新子节点:
patchChildren在更新 Fragment 时被复用