从 HTML 原生角度来看, 是一种声明式的占位容器,其内容不会立即渲染,而是等待 JavaScript 激活。
Vue 借用了这个语义,在模板编译阶段用它来封装多个元素或结构性指令(如 v-if, v-for, v-slot)的渲染逻辑。
特性 | 说明 |
---|---|
不会渲染成真实 DOM | 是的,运行时会“解开” |
编译阶段可识别 | Vue 编译器知道如何把它转换成渲染函数 |
提高语法灵活性 | 可包裹多个元素,使指令或插槽更加灵活 |
Vue 的模板语法要求一个组件只能有一个根节点。当你需要根据条件渲染多个元素、或在某些结构性指令(比如 v-if、v-for)中包裹多个元素时, 就派上用场了。
错误写法(多个根节点):
<div v-if="show">Adiv>
<span v-if="show">Bspan>
正确写法:
<template v-if="show">
<div>Adiv>
<span>Bspan>
template>
<template v-for="item in list" :key="item.id">
<h3>{{ item.title }}h3>
<p>{{ item.content }}p>
template>
这将为 list 中的每一项生成一组 h3 + p 标签。
可以与插槽搭配使用,形成具名插槽或作用域插槽:
<my-layout>
<template #header>
<h1>标题区域h1>
template>
<template #default>
<p>主体内容p>
template>
my-layout>
<my-list :items="books">
<template v-slot:default="slotProps">
<li>{{ slotProps.item.title }}li>
template>
my-list>
普通的 div 包裹(有 DOM 输出):
<div v-if="isVisible">
<p>Ap>
<p>Bp>
div>
渲染结果:
<div>
<p>Ap>
<p>Bp>
div>
使用 包裹(无 DOM 输出):
<template v-if="isVisible">
<p>Ap>
<p>Bp>
template>
渲染结果:
<p>Ap>
<p>Bp>
Vue 模板经过编译器转换为“渲染函数”。 在这一步不会生成一个 DOM 节点对应的虚拟节点(VNode),它的子元素会直接被提升为父节点的子节点。
例如:
<template v-if="ok">
<div>Adiv>
<div>Bdiv>
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:
<div v-if="show1">Adiv>
<div v-if="show2">Bdiv>
可以用 :
<template v-if="show">
<div>Adiv>
<div>Bdiv>
template>
这样是合法的,因为 Vue 在编译时可以识别 只是一个“包裹容器”,不会干扰逻辑。
作用域插槽是高级用法,必须配合 :
<slot name="header" />
<template #header>
<h1>标题h1>
template>
为什么不用
在写 renderless component(无渲染组件)时,组件的本质可能只输出 内容,这种场景下 也很适合控制结构但不污染渲染结果。
在 Vue 2 中,一个组件只能有一个根节点(你必须用一个 div 包起来),而 在组件模板中不能直接作为根节点,因为它不渲染。
但在 Vue 3 引入了 Fragment:允许组件返回多个根节点,Vue 运行时自动将多个子节点当作一个“片段”处理。这解放了 的束缚。
<template>
<h1>Titleh1>
<p>Contentp>
template>
这相当于内部使用了 Fragment 包裹。
<template v-for="item in list" :key="item.id">
template>
// 错误, 没有真实 DOM
this.$refs.myTemplate.style.color = ‘red’ // ❌ 会报错
层级 | 本质 |
---|---|
语法 | 包裹多个子节点,不渲染自身 |
编译 | 被“解开”为多个子节点 |
运行 | 没有虚拟 DOM 节点,只存在子节点 |
使用 | 在条件渲染、插槽、循环中控制结构 |
特性 | 语法糖、增强表达力、结构更清晰 |