Vue 3 内置组件完全指南
本文档从零开始讲解 Vue 3 的内置组件:Transition(单元素/组件过渡)、TransitionGroup(列表过渡)、KeepAlive(缓存组件)、Teleport(传送到其它 DOM)、Suspense(异步组件/异步 setup 的占位),配有大量示例,适合新手系统学习。
一、什么是“内置组件”?
1.1 概念
内置组件是 Vue 自带的、不需要你 import 就能在模板里直接用的特殊组件。
它们名字首字母大写(如 Transition、KeepAlive),在模板里可以写 或 (Vue 会识别)。
1.2 有哪些?
| 内置组件 | 作用简述 |
|---|---|
| Transition | 为单个元素或组件的进入/离开添加过渡动画 |
| TransitionGroup | 为列表中元素的增删、顺序变化添加过渡 |
| KeepAlive | 缓存不显示的组件实例,切换回来时保留状态 |
| Teleport | 把组件渲染到 DOM 的其它位置(如 body) |
| Suspense | 为异步组件或异步 setup 提供“加载中/失败”的占位 |
下面逐个详细说明和示例。
二、Transition:单元素/组件过渡
2.1 为什么需要?
用 v-if、v-show 或动态组件切换时,元素会瞬间出现/消失。
若希望出现时有淡入、离开时有淡出等动画,可以用 包住这个元素或组件,Vue 会在进入/离开的不同阶段自动加上/去掉你定义的 class,你只需写 CSS 或配合 JavaScript 钩子即可。
2.2 基本用法
用 包住一个根元素或一个组件(内部只能有一个根级子节点)。
当包住的节点被 v-if、v-show、动态组件 等控制“显示/隐藏”时,Vue 会在进入和离开时加上约定的 class,默认名字是 v-enter-from、v-enter-active、v-enter-to、v-leave-from、v-leave-active、v-leave-to。
示例:淡入淡出
<template>
<div>
<button @click="show = !show">切换</button>
<Transition>
<p v-if="show">你好,这是过渡内容</p>
</Transition>
</div>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<style scoped>
.v-enter-active,
.v-leave-active {
transition: opacity 0.3s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
- v-enter-from:进入的起始状态(还没进到最终位置/透明度)。
- v-enter-active:进入过程,一般在这里写 transition。
- v-enter-to:进入的结束状态。
- v-leave-from:离开的起始状态。
- v-leave-active:离开过程。
- v-leave-to:离开的结束状态(通常和 v-enter-from 一致,形成“淡出”)。
2.3 自定义 class 名前缀:name 属性
默认前缀是 v-,若想用 fade- 等,给 加 name=”fade”:
<Transition name="fade">
<p v-if="show">内容</p>
</Transition>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
2.4 与 v-show 一起用
Transition 也可以包 v-show,但 v-show 只是切换 display,离开时元素仍在 DOM 里,过渡仍会按“离开”阶段执行。
<Transition name="fade">
<p v-show="show">内容</p>
</Transition>
2.5 动态组件切换
包住 时,组件切换会触发离开(旧组件)和进入(新组件)的过渡:
<Transition name="slide">
<component :is="currentTab" />
</Transition>
2.6 只想要进入动画、不要离开动画
可以只写 enter 相关的 class,不写 leave,离开时就不会有过渡。
也可以配合 mode=”out-in” 或 mode=”in-out” 控制“先离开再进入”或“先进入再离开”的顺序。
<Transition name="fade" mode="out-in">
<component :is="current" />
</Transition>
- mode=”out-in”:先执行离开动画,结束后再执行进入动画。
- mode=”in-out”:先执行进入动画,再执行离开(较少用)。
2.7 使用 JavaScript 钩子(可选)
除了用 CSS class,还可以在 @before-enter、@enter、@after-enter、@before-leave、@leave、@after-leave 里写逻辑,配合动画库(如 GSAP)做更复杂动画。
enter 和 leave 里要在动画结束时调用 done(),否则 Vue 会一直等。
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
>
<p v-if="show">内容</p>
</Transition>
function onBeforeEnter(el) {
el.style.opacity = 0
}
function onEnter(el, done) {
el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300 }).finished.then(done)
}
function onAfterEnter(el) {
el.style.opacity = ''
}
三、TransitionGroup:列表过渡
3.1 为什么需要?
v-for 渲染的列表,当增删、排序时,希望每个项有进入/离开/移动的动画。
Transition 只能包一个根节点,不能直接包 v-for;TransitionGroup 专门用来包列表,并为每个子元素应用过渡,同时支持 move(移动)动画。
3.2 基本用法
用 包住 v-for 的容器,并给每个列表项设唯一的 key。
TransitionGroup 默认会渲染成 ,可用 tag 改成 ul、div 等。
<template>
<div>
<button @click="add">添加</button>
<button @click="remove">删除</button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in list" :key="item.id">
{{ item.text }}
</li>
</TransitionGroup>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, text: '项一' },
{ id: 2, text: '项二' }
])
function add() {
list.value.push({ id: Date.now(), text: '新项' })
}
function remove() {
list.value.pop()
}
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-move {
transition: transform 0.3s ease;
}
</style>
- list-enter-from / list-leave-to:进入起点、离开终点。
- list-move:列表项位置变化(重排)时的过渡,使“移动”有动画。
3.3 tag 与 tag 的样式
tag=”ul” 表示根元素渲染成 ,注意自己给 ul 去默认 list-style,或给 TransitionGroup 加 class 写样式。
四、KeepAlive:缓存组件
4.1 为什么需要?
用 v-if 或动态组件切换时,不显示的组件会被销毁,再切回来会重新创建,之前填的表、滚动位置都会丢。
KeepAlive 会把已经渲染过的组件实例缓存在内存里,切走时只是隐藏、不销毁,切回来时直接复用,状态得以保留。
4.2 基本用法
用 包住动态组件或 v-if 控制的单个组件(通常配合 或多个 v-if/v-else-if 的组件)。
<template>
<div>
<button @click="current = 'A'">Tab A</button>
<button @click="current = 'B'">Tab B</button>
<KeepAlive>
<component :is="current" />
</KeepAlive>
</div>
</template>
<script setup>
import { ref } from 'vue'
import CompA from './CompA.vue'
import CompB from './CompB.vue'
const current = ref('A')
</script>
- 从 A 切到 B 再切回 A 时,A 的输入、滚动位置等会保留。
- KeepAlive 只对直接子节点里一个组件生效(多个子节点时只缓存第一个),所以一般只包一个 。
4.3 include / exclude:只缓存部分组件
include:只有名字匹配的组件会被缓存(组件 name 或注册名)。
exclude:名字匹配的不缓存。
值可以是逗号分隔字符串、正则或数组。
<KeepAlive :include="['CompA', 'CompB']">
<component :is="current" />
</KeepAlive>
<KeepAlive exclude="CompB">
<component :is="current" />
</KeepAlive>
组件要有 name(在选项式里 export default { name: ‘CompA’ } 或在 里配合普通 写 name),include/exclude 才生效。
4.4 max:最多缓存几个实例
max 表示最多缓存多少个组件实例,超过时会把最久未使用的实例销毁。
<KeepAlive :max="5">
<component :is="current" />
</KeepAlive>
4.5 生命周期:onActivated / onDeactivated
被 KeepAlive 缓存的组件,不会再触发 onUnmounted(因为没销毁),而是触发 onActivated(被重新显示时)和 onDeactivated(被隐藏时)。
适合在 onActivated 里刷新数据、onDeactivated 里暂停轮询等。
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
console.log('组件被激活(显示)')
})
onDeactivated(() => {
console.log('组件被停用(隐藏)')
})
五、Teleport:传送到其它 DOM
5.1 为什么需要?
例如弹窗、Toast 要盖住整个页面,若写在某个深层组件里,会受父级的 overflow: hidden、z-index 等影响,不好做“全屏遮罩”。
Teleport 可以把一段模板渲染到 body 或其它 DOM 节点,逻辑上仍在当前组件(仍可用当前组件的 props、emit、状态),只是 DOM 挂载点变了。
5.2 基本用法
用 包住要“传送”的内容,to 是 CSS 选择器或 “body”。
<template>
<div>
<button @click="show = true">打开弹窗</button>
<Teleport to="body">
<div v-if="show" class="modal">
<p>弹窗内容</p>
<button @click="show = false">关闭</button>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
<style scoped>
.modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
</style>
- 点击“打开弹窗”后,modal 的 DOM 会出现在 下,不受父级样式影响。
- show 仍是当前组件的 ref,逻辑不变。
5.3 to 的值
- “body”:挂到 document.body。
- “#app”:挂到 id 为 app 的节点。
- “.container”:挂到第一个 class 为 container 的节点。
- 要挂的节点必须已经存在于页面上(一般用 id 或 body)。
5.4 条件性禁用传送:disabled
disabled 为 true 时,不传送,子内容留在当前父节点下渲染。
<Teleport to="body" :disabled="isMobile">
<div class="modal">...</div>
</Teleport>
六、Suspense:异步组件与异步 setup(实验性)
6.1 为什么需要?
异步组件(动态 import)或异步 setup(async setup)在“加载中”时,若没有占位,页面可能空白或布局闪动。
Suspense 提供两个插槽:#default(真正要渲染的异步内容)和 #fallback(加载中显示的占位),Vue 会在异步未完成时显示 fallback,完成后显示 default。
6.2 基本用法
用 包住异步组件或内部有异步 setup 的组件,并提供一个 #fallback:
<template>
<Suspense>
<template #default>
<AsyncComp />
</template>
<template #fallback>
<p>加载中...</p>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
</script>
- AsyncComp 加载完成前显示“加载中…”,完成后显示 AsyncComp。
- 若 AsyncComp 的 setup 是 async 且返回 Promise,Suspense 也会等该 Promise resolve 后再显示 #default。
6.3 与 defineAsyncComponent 配合
defineAsyncComponent 用来定义“异步加载”的组件,可配合 loadingComponent、delay、timeout 等;和 Suspense 一起用时,Suspense 的 #fallback 就是“加载中”的 UI。
const AsyncComp = defineAsyncComponent({
loader: () => import('./Heavy.vue'),
delay: 200,
timeout: 3000
})
注意:Suspense 在 Vue 3 中仍被视为实验性,API 可能调整,生产环境使用前建议查官方文档确认。
七、内置组件速查表
| 内置组件 | 作用 | 常用属性/插槽 |
|---|---|---|
| Transition | 单元素/组件进入离开过渡 | name、mode;CSS:v-enter-from/active/to、v-leave-from/active/to |
| TransitionGroup | 列表增删、排序过渡 | name、tag;CSS:xxx-move |
| KeepAlive | 缓存组件实例 | include、exclude、max;子组件有 onActivated/onDeactivated |
| Teleport | 把内容渲染到其它 DOM | to(选择器或 “body”)、disabled |
| Suspense | 异步组件/async setup 占位 | #default、#fallback |
八、学习建议
- Transition:先会写 name + 六段 class(enter-from/active/to、leave-from/active/to),再做“淡入淡出”“滑动”等效果;需要再学 mode、JavaScript 钩子。
- TransitionGroup:包 v-for 列表,设 key,写 xxx-move 让重排有动画。
- KeepAlive:包 ,需要时加 include/exclude/max,在子组件里用 onActivated/onDeactivated。
- Teleport:弹窗、Toast 等用 to=”body” 挂到 body,避免被父级裁剪。
- Suspense:异步组件或 async setup 时用 #default + #fallback,注意其实验性。
把本文档里的 Transition、KeepAlive、Teleport 示例在项目里敲一遍,会掌握得更牢。祝你学习顺利。