🧠 是什么?
从 HTML 原生角度来看, 是一种声明式的占位容器,其内容不会立即渲染,而是等待 JavaScript 激活。
Vue 借用了这个语义,在模板编译阶段用它来封装多个元素或结构性指令(如 v-if, v-for, v-slot)的渲染逻辑。
特点总结:
特性 | 说明 |
---|---|
不会渲染成真实 DOM | 是的,运行时会“解开” |
编译阶段可识别 | Vue 编译器知道如何把它转换成渲染函数 |
提高语法灵活性 | 可包裹多个元素,使指令或插槽更加灵活 |
✅ 基本作用
Vue 的模板语法要求一个组件只能有一个根节点。当你需要根据条件渲染多个元素、或在某些结构性指令(比如 v-if、v-for)中包裹多个元素时, 就派上用场了。
🧩 用法场景
多元素条件渲染 (v-if)
错误写法(多个根节点):
<!-- ❌ 语法错误 -->
<div v-if="show">A</div>
<span v-if="show">B</span>
正确写法:
<template v-if="show">
<div>A</div>
<span>B</span>
</template>
循环多个元素 (v-for)
<template v-for="item in list" :key="item.id">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
</template>
这将为 list 中的每一项生成一组 h3 + p 标签。
插槽中的 template
可以与插槽搭配使用,形成具名插槽或作用域插槽:
<!-- 父组件 -->
<my-layout>
<template #header>
<h1>标题区域</h1>
</template>
<template #default>
<p>主体内容</p>
</template>
</my-layout>
使用 v-slot 的作用域插槽
<my-list :items="books">
<template v-slot:default="slotProps">
<li>{{ slotProps.item.title }}</li>
</template>
</my-list>
⚠️ 注意事项
- 不会在 DOM 中渲染为标签,只是一个包裹容器。
- 使用 v-if、v-for 时,如果控制多个元素,必须用 包裹。
- v-for 的 key 应写在 上,而不是子元素。
👀 示例对比
普通的 div 包裹(有 DOM 输出):
<div v-if="isVisible">
<p>A</p>
<p>B</p>
</div>
渲染结果:
<div>
<p>A</p>
<p>B</p>
</div>
使用 包裹(无 DOM 输出):
<template v-if="isVisible">
<p>A</p>
<p>B</p>
</template>
渲染结果:
<p>A</p>
<p>B</p>
🛠️ 编译阶段发生了什么?
Vue 模板经过编译器转换为“渲染函数”。 在这一步不会生成一个 DOM 节点对应的虚拟节点(VNode),它的子元素会直接被提升为父节点的子节点。
例如:
<template v-if="ok">
<div>A</div>
<div>B</div>
</template>
经过编译后会变成(简化版):
ok ? [h('div', 'A'), h('div', 'B')] : null
你可以看到 自身并不会成为虚拟节点,它只是一种包裹结构的语法糖。
⚙️ 运行时行为 —— 它“消失了”
在运行时,Vue 渲染引擎(比如 vDOM diff + patch)完全忽略 ,直接处理它内部的子节点。
举例:在 v-for 的时候,如果你用的是 ,每个 item 会生成它内部的一整组 DOM 元素,而不是一个额外的 标签。
<template v-for="item in list" :key="item.id">
<p>{{ item.name }}</p>
<span>{{ item.desc }}</span>
</template>
生成:
<p>...</p>
<span>...</span>
<p>...</p>
<span>...</span>
...
不会生成 元素。
🧩 使用场景深度理解
条件渲染多个元素时
Vue 不允许根节点下并列使用两个 v-if:
<!-- 错误:不能并列两个元素上 v-if -->
<div v-if="show1">A</div>
<div v-if="show2">B</div>
可以用 :
<template v-if="show">
<div>A</div>
<div>B</div>
</template>
这样是合法的,因为 Vue 在编译时可以识别 只是一个“包裹容器”,不会干扰逻辑。
插槽(Slot)系统的核心容器
作用域插槽是高级用法,必须配合 :
<!-- 子组件中 -->
<slot name="header" />
<!-- 父组件中 -->
<template #header>
<h1>标题</h1>
</template>
为什么不用
高阶组件和逻辑抽象的“透明容器”
在写 renderless component(无渲染组件)时,组件的本质可能只输出 内容,这种场景下 也很适合控制结构但不污染渲染结果。
🔬 与 Fragment(片段)的关系(Vue 3 特有)
在 Vue 2 中,一个组件只能有一个根节点(你必须用一个 div 包起来),而 在组件模板中不能直接作为根节点,因为它不渲染。
但在 Vue 3 引入了 Fragment:允许组件返回多个根节点,Vue 运行时自动将多个子节点当作一个“片段”处理。这解放了 的束缚。
<!-- Vue 3 中合法 -->
<template>
<h1>Title</h1>
<p>Content</p>
</template>
这相当于内部使用了 Fragment 包裹。
⚠️ 常见误区
误以为 会渲染出元素:
- 它不会。你不能给它加 class、style,它不会出现。
忘记为 v-for 中的 设置 key:
<template v-for="item in list" :key="item.id">
<!-- 正确做法 -->
</template>
把 当成结构节点来传入 DOM 操作函数,结果找不到:
// 错误, 没有真实 DOM
this.$refs.myTemplate.style.color = ‘red’ // ❌ 会报错
✅ 总结精炼:
层级 | 本质 |
---|---|
语法 | 包裹多个子节点,不渲染自身 |
编译 | 被“解开”为多个子节点 |
运行 | 没有虚拟 DOM 节点,只存在子节点 |
使用 | 在条件渲染、插槽、循环中控制结构 |
特性 | 语法糖、增强表达力、结构更清晰 |