内置组件(下)—— TransitionGroup、KeepAlive、Teleport
本教程共 40 篇 · 第 33 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
33. 内置组件(下)—— TransitionGroup、KeepAlive、Teleport
本节目标:学会三个内置组件——列表动画、组件缓存和 DOM 传送门。
TransitionGroup——列表动画
<TransitionGroup> 是 <Transition> 的列表版本,专门给 v-for 渲染的列表添加进入、离开和移动动画。
跟 <Transition> 的区别:
| 对比 | Transition | TransitionGroup |
|---|---|---|
| 包装元素 | 不渲染额外元素 | 默认不渲染,可用 tag 指定 |
| 过渡模式 | 支持 mode | 不支持 |
| key | 可选 | 必须 |
| 类名作用域 | 根元素 | 列表中的每个元素 |
基本用法:
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
移动动画
上面的例子有个问题:插入或删除元素时,周围元素会”跳”过去,而不是平滑移动。
加上 .list-move 类名解决:
.list-move, /* 移动中的元素 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 离开的元素脱离文档流,不影响其他元素定位 */
.list-leave-active {
position: absolute;
}
Note
position: absolute很关键。不加的化,离开的元素还占着位置,其他元素没法平滑移动过去。
交错动画
通过 JavaScript Hooks 和 data-* 属性,可以实现列表元素的交错进入效果:
<TransitionGroup
tag="ul"
:css="false"
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
>
<li
v-for="(item, index) in items"
:key="item.msg"
:data-index="index"
>
{{ item.msg }}
</li>
</TransitionGroup>
function onEnter(el, done) {
gsap.to(el, {
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15, // 每个元素延迟 0.15s
onComplete: done
})
}
KeepAlive——组件缓存
动态切换组件时,默认离开的组件会被销毁,再切回来时状态全丢了。<KeepAlive> 能缓存组件实例,保留状态。
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
选择性缓存
用 include 和 exclude 控制哪些组件需要缓存:
<!-- 字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则 -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
TipVue 3.2.34+ 的
<script setup>会自动根据文件名推断组件名,不需要手动声明name。
最大缓存数
用 max 限制缓存数量,超出时按 LRU 策略淘汰最久没用的:
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
缓存组件的生命周期
被 <KeepAlive> 缓存的组件有两个特殊生命周期:
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 首次挂载 + 每次从缓存恢复时调用
})
onDeactivated(() => {
// 从 DOM 移入缓存时调用
})
Note
onActivated在首次挂载时也会调用,onDeactivated在组件真正卸载时也会调用。
Teleport——传送门
<Teleport> 可以把模板的一部分”传送”到 DOM 树的其他位置。最典型的场景是模态框。
为什么需要 Teleport
模态框的代码逻辑上属于某个组件,但视觉上要覆盖全屏。如果模态框嵌在 DOM 树深处,position: fixed 可能因为祖先元素的 transform 属性失效,z-index 也可能被其他元素覆盖。
基本用法
<button @click="open = true">打开模态框</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>模态框内容</p>
<button @click="open = false">关闭</button>
</div>
</Teleport>
to 属性接受 CSS 选择器或 DOM 节点。上面这个例子把模态框传送到 <body> 下,脱离了原来的嵌套层级。
Tip
<Teleport>只改变 DOM 结构,不改变组件的逻辑层级。Props 传递、事件触发、inject 都正常工作。
禁用传送
用 disabled 属性可以临时关闭传送:
<Teleport :disabled="isMobile">
<div class="modal">...</div>
</Teleport>
移动端可能希望模态框就地渲染,桌面端才传送到 body。
多个 Teleport 到同一目标
多个 <Teleport> 可以传送到同一个目标,内容按顺序追加:
<Teleport to="#modals">
<div>A</div>
</Teleport>
<Teleport to="#modals">
<div>B</div>
</Teleport>
渲染结果:
<div id="modals">
<div>A</div>
<div>B</div>
</div>
延迟传送(Vue 3.5+)
defer 属性让 Teleport 等目标元素渲染完成后再挂载:
<Teleport defer to="#late-div">...</Teleport>
<!-- 模板后面才渲染的目标 -->
<div id="late-div"></div>
Note目标元素必须在同一个渲染周期内出现。如果目标要等异步数据才渲染,Teleport 会报错。
本节回顾
<TransitionGroup>给列表添加进入/离开/移动动画,每个元素必须有key- 移动动画需要
.list-move类名 +position: absolute <KeepAlive>缓存组件实例,保留切换前的状态include/exclude控制缓存范围,max限制缓存数量- 缓存组件用
onActivated/onDeactivated钩子 <Teleport>把模板传送到 DOM 树的其他位置,适合模态框disabled可以临时关闭传送,defer可以延迟挂载