在 Vue 开发中,除了我们日常编写的业务组件外,框架还提供了一系列内置组件,它们为我们处理常见的开发场景提供了优雅的解决方案。今天,我们就来深入探讨 Vue 的五大内置组件:
Transition
、TransitionGroup
、KeepAlive
、Teleport
和Suspense
。
1. Transition - 丝滑的过渡动画
什么是 Transition?
Transition 组件用于在元素或组件的插入、更新和移除时添加动画效果,让用户体验更加流畅。
基本使用
js
<template>
<button @click="show = !show">切换</button>
<Transition name="fade">
<p v-if="show">Hello, Vue!</p>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
过渡类名详解
Transition 组件会自动应用以下 6 个 CSS 类名:
v-enter-from
:进入动画的起始状态v-enter-active
:进入动画的激活状态v-enter-to
:进入动画的结束状态v-leave-from
:离开动画的起始状态v-leave-active
:离开动画的激活状态v-leave-to
:离开动画的结束状态
注意:其中的 v
是默认前缀,可以通过 name
属性自定义。
JavaScript 钩子
除了 CSS 过渡,还可以使用 JavaScript 钩子:
js
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<div v-if="show">内容</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
const onBeforeEnter = (el) => {
// 元素插入 DOM 前的回调
}
const onEnter = (el, done) => {
// 元素插入 DOM 后的回调
// 需要手动调用 done() 来结束过渡
done()
}
</script>
模式控制
js
<Transition mode="out-in">
<button :key="isEditing" @click="isEditing = !isEditing">
{{ isEditing ? '保存' : '编辑' }}
</button>
</Transition>
支持的模式:
in-out
:新元素先进入,当前元素后离开out-in
:当前元素先离开,新元素后进入
2. TransitionGroup - 列表过渡专家
什么是 TransitionGroup?
TransitionGroup 组件专门用于处理动态列表中元素的进入、离开和移动的动画效果。
基本使用
js
<template>
<button @click="addItem">添加</button>
<button @click="removeItem">移除</button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</TransitionGroup>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, text: '项目 1' },
{ id: 2, text: '项目 2' },
{ id: 3, text: '项目 3' }
])
let nextId = 4
const addItem = () => {
items.value.push({ id: nextId++, text: `项目 ${nextId}` })
}
const removeItem = () => {
items.value.pop()
}
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from {
opacity: 0;
transform: translateX(30px);
}
.list-leave-to {
opacity: 0;
transform: translateX(-30px);
}
/* 确保离开的元素脱离文档流 */
.list-leave-active {
position: absolute;
}
</style>
关键特性
- 必须设置 key:每个元素都需要唯一的 key
- 支持 CSS 变换:自动检测元素位置变化应用移动动画
- tag 属性:指定包裹容器的标签,默认为不渲染包裹元素
3. KeepAlive - 组件缓存大师
什么是 KeepAlive?
KeepAlive 组件用于缓存不活动的组件实例,避免重复渲染,保持组件状态。
基本使用
js
<template>
<div>
<button @click="currentTab = 'Home'">首页</button>
<button @click="currentTab = 'About'">关于</button>
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</div>
</template>
<script setup>
import { ref, shallowRef } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const currentTab = ref('Home')
const tabs = {
Home,
About
}
</script>
高级配置
js
<KeepAlive
:include="/Home|About/"
:exclude="['Settings']"
:max="10"
>
<component :is="currentComponent" />
</KeepAlive>
- include:只有名称匹配的组件会被缓存(字符串、正则或数组)
- exclude:任何名称匹配的组件都不会被缓存
- max:最多可缓存的组件实例数量
生命周期钩子
被缓存的组件会获得两个新的生命周期钩子:
js
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 组件被激活时调用
console.log('组件激活')
})
onDeactivated(() => {
// 组件被停用时调用
console.log('组件停用')
})
</script>
4. Teleport - 任意门组件
什么是 Teleport?
Teleport 组件允许我们将组件模板的一部分"传送"到 DOM 中的其他位置,而不影响组件的逻辑关系。
基本使用
js
<template>
<div class="app">
<button @click="showModal = true">打开模态框</button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<h2>模态框标题</h2>
<p>这是模态框内容</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
}
</style>
多个 Teleport 到同一目标
js
<template>
<Teleport to="#modals">
<div>第一个模态框</div>
</Teleport>
<Teleport to="#modals">
<div>第二个模态框</div>
</Teleport>
</template>
渲染结果:
html
<div id="modals">
<div>第一个模态框</div>
<div>第二个模态框</div>
</div>
禁用 Teleport
js
<Teleport to="body" :disabled="isMobile">
<div>内容</div>
</Teleport>
5. Suspense - 异步组件管家
什么是 Suspense?
Suspense 组件用于协调组件树中的异步依赖,在等待异步组件时显示加载状态。
基本使用
js
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
<style scoped>
.loading {
text-align: center;
padding: 20px;
color: #666;
}
</style>
异步 setup 组件
js
<template>
<Suspense>
<template #default>
<ComponentWithAsyncSetup />
</template>
<template #fallback>
<div>加载用户数据...</div>
</template>
</Suspense>
</template>
<script setup>
import { ref } from 'vue'
const ComponentWithAsyncSetup = {
async setup() {
const userData = await fetchUserData()
return { userData }
},
template: `<div>用户: {{ userData.name }}</div>`
}
async function fetchUserData() {
// 模拟 API 调用
return new Promise(resolve => {
setTimeout(() => {
resolve({ name: 'Vue 开发者' })
}, 2000)
})
}
</script>
事件处理
js
<template>
<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
<AsyncComponent />
</Suspense>
</template>
<script setup>
const onPending = () => {
console.log('开始加载异步组件')
}
const onResolve = () => {
console.log('异步组件加载完成')
}
const onFallback = () => {
console.log('显示加载状态')
}
</script>
实战案例:组合使用内置组件
让我们看一个综合使用多个内置组件的例子:
js
<template>
<div class="app">
<!-- 标签页切换 -->
<nav>
<button
v-for="tab in tabs"
:key="tab.id"
@click="currentTab = tab.id"
>
{{ tab.name }}
</button>
</nav>
<!-- 主要内容区域 -->
<main>
<KeepAlive>
<Transition name="slide" mode="out-in">
<Suspense>
<template #default>
<component :is="currentTabComponent" />
</template>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</Transition>
</KeepAlive>
</main>
<!-- 全局通知 -->
<Teleport to="#notifications">
<TransitionGroup name="notification">
<div
v-for="notification in notifications"
:key="notification.id"
class="notification"
>
{{ notification.message }}
</div>
</TransitionGroup>
</Teleport>
</div>
</template>
<script setup>
import { ref, computed, defineAsyncComponent } from 'vue'
// 标签页状态
const currentTab = ref('home')
const tabs = [
{ id: 'home', name: '首页' },
{ id: 'profile', name: '个人资料' },
{ id: 'settings', name: '设置' }
]
// 异步组件
const currentTabComponent = computed(() =>
defineAsyncComponent(() => import(`./${currentTab.value}.vue`))
)
// 通知系统
const notifications = ref([])
// 添加通知
const addNotification = (message) => {
const id = Date.now()
notifications.value.push({ id, message })
setTimeout(() => {
notifications.value = notifications.value.filter(n => n.id !== id)
}, 3000)
}
</script>
<style scoped>
.slide-enter-active,
.slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter-from {
opacity: 0;
transform: translateX(50px);
}
.slide-leave-to {
opacity: 0;
transform: translateX(-50px);
}
.notification-enter-active,
.notification-leave-active {
transition: all 0.3s ease;
}
.notification-enter-from {
opacity: 0;
transform: translateY(-30px);
}
.notification-leave-to {
opacity: 0;
transform: translateX(100%);
}
.loading {
text-align: center;
padding: 50px;
font-size: 18px;
}
</style>
总结
Vue 的内置组件为我们提供了强大的工具来处理常见的开发需求:
- Transition:为单个元素/组件添加过渡动画
- TransitionGroup:为列表项添加排序和动画效果
- KeepAlive:缓存组件状态,提升性能
- Teleport:将内容渲染到 DOM 任意位置
- Suspense:优雅处理异步组件加载状态
这些内置组件不仅功能强大,而且可以灵活组合使用,帮助我们构建更加优雅、高效的 Vue 应用。掌握它们的使用,将让你的 Vue 开发技能更上一层楼!
希望这篇文章能帮助你全面理解 Vue 的内置组件,并在实际项目中灵活运用。Happy Coding! 🚀