概述
Vue 2 提供了一系列内置组件,这些组件无需注册即可在任何组件模板中直接使用。内置组件主要用于实现动态组件、过渡动画、组件缓存和内容分发等核心功能。
内置组件列表
| 组件 | 用途 | 是否渲染真实 DOM |
|---|---|---|
component | 动态组件渲染 | 是 |
transition | 单元素/组件过渡动画 | 否(抽象组件) |
transition-group | 多元素/组件过渡动画 | 是 |
keep-alive | 组件实例缓存 | 否(抽象组件) |
slot | 内容分发插槽 | 否(插槽占位符) |
component
功能描述
<component> 是一个动态组件容器,根据 is 属性的值动态渲染不同的组件。它允许在运行时决定渲染哪个组件,是实现组件动态切换的核心方式。
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
is | string | ComponentDefinition | ComponentConstructor | 是 | 要渲染的组件名称或组件定义对象 |
inline-template | boolean | 否 | 是否将组件内容作为内联模板 |
基础用法
<div id="app">
<button @click="currentComponent = 'Home'">首页</button>
<button @click="currentComponent = 'About'">关于</button>
<button @click="currentComponent = 'Contact'">联系</button>
<component :is="currentComponent"></component>
</div>new Vue({
el: "#app",
data: {
currentComponent: "Home"
},
components: {
Home: {
template: "<div>首页内容</div>"
},
About: {
template: "<div>关于我们</div>"
},
Contact: {
template: "<div>联系方式</div>"
}
}
})高级用法
使用组件对象引用
<component :is="$options.components.child"></component>结合计算属性动态选择组件
<template>
<div>
<input v-model="viewType" type="radio" value="list" /> 列表视图
<input v-model="viewType" type="radio" value="grid" /> 网格视图
<component :is="currentView" :items="items"></component>
</div>
</template>
<script>
export default {
data() {
return {
viewType: "list",
items: [1, 2, 3, 4, 5]
}
},
computed: {
currentView() {
return this.viewType === "list" ? "ListView" : "GridView"
}
},
components: {
ListView: {
props: ["items"],
template: '<ul><li v-for="item in items" :key="item">{{ item }}</li></ul>'
},
GridView: {
props: ["items"],
template:
'<div class="grid"><span v-for="item in items" :key="item">{{ item }}</span></div>'
}
}
}
</script>注意事项
- 动态组件切换时,默认会销毁旧组件并创建新组件,状态不会保留
- 如需保留状态,请配合
<keep-alive>使用 is属性可以是已注册的组件名、组件选项对象或组件构造函数
transition
功能描述
<transition> 为单个元素或组件提供进入/离开过渡动画效果。它是一个抽象组件,不会渲染真实的 DOM 元素,只负责管理过渡动画的生命周期。
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | 'v' | 自动生成 CSS 过渡类名的前缀 |
appear | boolean | false | 是否在初始渲染时应用过渡 |
css | boolean | true | 是否使用 CSS 过渡类 |
type | string | - | 指定过渡结束事件类型:'transition' 或 'animation' |
mode | string | - | 控制过渡时序:'out-in' 或 'in-out' |
duration | number | object | - | 显式指定过渡持续时间 |
enter-class | string | - | 自定义进入起始状态类名 |
enter-active-class | string | - | 自定义进入过渡类名 |
enter-to-class | string | - | 自定义进入结束状态类名 |
leave-class | string | - | 自定义离开起始状态类名 |
leave-active-class | string | - | 自定义离开过渡类名 |
leave-to-class | string | - | 自定义离开结束状态类名 |
appear-class | string | - | 自定义初始渲染起始状态类名 |
appear-active-class | string | - | 自定义初始渲染过渡类名 |
appear-to-class | string | - | 自定义初始渲染结束状态类名 |
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
before-enter | el | 进入过渡开始前触发 |
enter | el, done | 进入过渡开始时触发 |
after-enter | el | 进入过渡结束后触发 |
enter-cancelled | el | 进入过渡被取消时触发 |
before-leave | el | 离开过渡开始前触发 |
leave | el, done | 离开过渡开始时触发 |
after-leave | el | 离开过渡结束后触发 |
leave-cancelled | el | 离开过渡被取消时触发(仅 v-show) |
before-appear | el | 初始渲染过渡开始前触发 |
appear | el, done | 初始渲染过渡开始时触发 |
after-appear | el | 初始渲染过渡结束后触发 |
appear-cancelled | el | 初始渲染过渡被取消时触发 |
CSS 过渡类名
过渡过程中,Vue 会自动添加/移除以下 CSS 类名(以 name="fade" 为例):
fade-enter → 进入起始状态
fade-enter-active → 进入过渡状态
fade-enter-to → 进入结束状态
fade-leave → 离开起始状态
fade-leave-active → 离开过渡状态
fade-leave-to → 离开结束状态基础用法
<div id="app">
<button @click="show = !show">切换</button>
<transition name="fade">
<p v-if="show">Hello, Vue!</p>
</transition>
</div>.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}new Vue({
el: "#app",
data: {
show: true
}
})过渡模式
当同时有元素进入和离开时,可以使用 mode 属性控制时序:
<transition name="fade" mode="out-in">
<button v-if="isEditing" key="save" @click="isEditing = false">保存</button>
<button v-else key="edit" @click="isEditing = true">编辑</button>
</transition>out-in:当前元素先离开,新元素等待旧元素离开后再进入in-out:新元素先进入,旧元素等待新元素进入后再离开
JavaScript 钩子
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave">
<div v-if="show"></div>
</transition>methods: {
beforeEnter(el) {
el.style.opacity = 0
},
enter(el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
afterEnter(el) {
console.log('进入动画完成')
},
beforeLeave(el) {
console.log('离开动画即将开始')
},
leave(el, done) {
Velocity(el, { opacity: 0 }, { duration: 300, complete: done })
},
afterLeave(el) {
console.log('离开动画完成')
}
}结合动态组件
<transition name="fade" mode="out-in">
<component :is="currentComponent"></component>
</transition>初始渲染过渡
<transition appear name="fade">
<p>首次渲染时也会应用过渡效果</p>
</transition>transition-group
功能描述
<transition-group> 为多个元素或组件列表提供过渡动画效果。与 <transition> 不同,它会渲染一个真实的 DOM 元素,并支持列表项的移动动画。
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tag | string | 'span' | 渲染的容器元素标签名 |
move-class | string | - | 自定义移动过渡类名 |
| 其他属性 | - | - | 除 mode 外,支持 <transition> 的所有属性 |
事件
支持 <transition> 的所有事件。
列表排序动画
<transition-group> 使用 FLIP 动画技术实现平滑的位置移动动画:
<div id="app">
<button @click="shuffle">随机排序</button>
<transition-group name="list" tag="ul">
<li v-for="item in items" :key="item.id" :key="item.id">{{ item.text }}</li>
</transition-group>
</div>.list-enter-active,
.list-leave-active {
transition: all 0.5s;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-move {
transition: transform 0.5s;
}new Vue({
el: "#app",
data: {
items: [
{ id: 1, text: "项目一" },
{ id: 2, text: "项目二" },
{ id: 3, text: "项目三" },
{ id: 4, text: "项目四" },
{ id: 5, text: "项目五" }
]
},
methods: {
shuffle() {
this.items = _.shuffle(this.items)
}
}
})列表增删动画
<div id="app">
<input v-model="newItem" @keyup.enter="addItem" />
<button @click="addItem">添加</button>
<transition-group name="list" tag="ul">
<li v-for="(item, index) in items" :key="item.id">
{{ item.text }}
<button @click="removeItem(index)">删除</button>
</li>
</transition-group>
</div>new Vue({
el: "#app",
data: {
newItem: "",
items: [],
nextId: 1
},
methods: {
addItem() {
if (this.newItem.trim()) {
this.items.push({
id: this.nextId++,
text: this.newItem.trim()
})
this.newItem = ""
}
},
removeItem(index) {
this.items.splice(index, 1)
}
}
})注意事项
- 必须为每个子元素设置唯一的
key,否则动画无法正常工作 - 使用
v-for时,key的值必须是唯一且稳定的,避免使用索引作为 key - 移动动画依赖 CSS
transform属性,确保元素的position样式允许移动
keep-alive
功能描述
<keep-alive> 用于缓存不活动的组件实例,避免组件切换时被销毁和重新创建。它是一个抽象组件,不会渲染真实的 DOM 元素。被缓存的组件会触发 activated 和 deactivated 生命周期钩子。
Props
| 属性 | 类型 | 说明 |
|---|---|---|
include | string | RegExp | Array | 只有名称匹配的组件会被缓存 |
exclude | string | RegExp | Array | 名称匹配的组件不会被缓存 |
max | number | 最多缓存多少组件实例 |
生命周期钩子
| 钩子 | 触发时机 |
|---|---|
activated | 组件被激活(从缓存中恢复或首次创建)时调用 |
deactivated | 组件被停用(切换到其他组件)时调用 |
基础用法
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>条件缓存
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>结合 transition 使用
<transition name="fade" mode="out-in">
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</transition>include 和 exclude
支持多种匹配方式:
<keep-alive include="Home,About">
<component :is="currentComponent"></component>
</keep-alive>
<keep-alive :include="/Home|About/">
<component :is="currentComponent"></component>
</keep-alive>
<keep-alive :include="['Home', 'About']">
<component :is="currentComponent"></component>
</keep-alive>
<keep-alive exclude="Settings">
<component :is="currentComponent"></component>
</keep-alive>匹配规则:首先检查组件的 name 选项,如果 name 不存在,则匹配局部注册名称(父组件 components 选项的键)。匿名组件无法被匹配。
限制缓存数量
<keep-alive :max="10">
<component :is="currentComponent"></component>
</keep-alive>当缓存的实例数量超过 max 值时,最久未访问的实例会被销毁(LRU 策略)。
完整示例
<div id="app">
<nav>
<button @click="current = 'Home'">首页</button>
<button @click="current = 'Posts'">文章</button>
<button @click="current = 'Archive'">归档</button>
</nav>
<keep-alive>
<component :is="current"></component>
</keep-alive>
</div>const Home = {
template: "<div>首页 - {{ time }}</div>",
data() {
return {
time: new Date().toLocaleTimeString()
}
},
activated() {
console.log("Home 被激活")
},
deactivated() {
console.log("Home 被停用")
}
}
const Posts = {
template: "<div>文章列表</div>",
activated() {
console.log("Posts 被激活")
},
deactivated() {
console.log("Posts 被停用")
}
}
const Archive = {
template: "<div>归档</div>",
activated() {
console.log("Archive 被激活")
},
deactivated() {
console.log("Archive 被停用")
}
}
new Vue({
el: "#app",
data: {
current: "Home"
},
components: { Home, Posts, Archive }
})注意事项
<keep-alive>只能包含一个直属子组件- 如果包含
v-for,将不会正常工作 - 函数式组件没有实例,无法被缓存
- 被缓存的组件不会触发
created、mounted等钩子,应使用activated替代
slot
功能描述
<slot> 是组件内容分发的出口,用于在组件模板中定义插槽位置。父组件传入的内容会被插入到 <slot> 标签所在的位置。
Props
| 属性 | 类型 | 说明 |
|---|---|---|
name | string | 插槽名称,用于具名插槽 |
默认插槽
<my-component>
<p>这段内容会被插入到组件的默认插槽中</p>
</my-component><template id="my-component">
<div class="container">
<slot>默认内容(当父组件未提供内容时显示)</slot>
</div>
</template>具名插槽
<base-layout>
<template v-slot:header>
<h1>页面标题</h1>
</template>
<template v-slot:default>
<p>主要内容</p>
</template>
<template v-slot:footer>
<p>页脚信息</p>
</template>
</base-layout><template id="base-layout">
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>作用域插槽
作用域插槽允许子组件向插槽传递数据:
<todo-list>
<template v-slot:default="slotProps">
<span class="custom-item"> {{ slotProps.item.text }} - {{ slotProps.item.priority }} </span>
</template>
</todo-list><template id="todo-list">
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item"></slot>
</li>
</ul>
</template>Vue.component("todo-list", {
template: "#todo-list",
data() {
return {
items: [
{ id: 1, text: "任务一", priority: "高" },
{ id: 2, text: "任务二", priority: "中" },
{ id: 3, text: "任务三", priority: "低" }
]
}
}
})解构插槽 Props
<todo-list>
<template v-slot="{ item }">
<span>{{ item.text }}</span>
</template>
</todo-list>
<todo-list>
<template v-slot="{ item: todo }">
<span>{{ todo.text }}</span>
</template>
</todo-list>
<todo-list>
<template v-slot="{ item, index = 0 }">
<span>{{ index }}: {{ item.text }}</span>
</template>
</todo-list>插槽语法糖
v-slot 可缩写为 #:
<base-layout>
<template #header>
<h1>页面标题</h1>
</template>
<template #default>
<p>主要内容</p>
</template>
<template #footer>
<p>页脚信息</p>
</template>
</base-layout>常见问题解答
Q1: 动态组件切换时如何保留状态?
使用 <keep-alive> 包裹 <component>:
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>Q2: transition 动画不生效怎么办?
检查以下几点:
- 确保 CSS 类名正确(
name-enter、name-enter-active等) - 确保过渡元素有
v-if、v-show或动态key - 检查 CSS
transition或animation属性是否正确设置
Q3: transition-group 列表动画错乱?
确保:
- 每个列表项有唯一且稳定的
key(不要使用索引) - 为移动动画添加
.name-move类 - 列表项的 CSS 不影响布局流(避免
display: inline-block)
Q4: keep-alive 缓存的组件数据不更新?
使用 activated 钩子在组件激活时刷新数据:
export default {
activated() {
this.fetchData()
}
}Q5: 如何在 keep-alive 中强制刷新某个组件?
使用 :key 强制重新创建:
<keep-alive>
<component :is="currentComponent" :key="componentKey"></component>
</keep-alive>data() {
return {
componentKey: 0
}
},
methods: {
forceRefresh() {
this.componentKey++
}
}最佳实践
1. 动态组件命名规范
使用语义化的组件名称,便于调试和维护:
components: {
UserProfile: () => import('./UserProfile.vue'),
UserSettings: () => import('./UserSettings.vue'),
UserActivity: () => import('./UserActivity.vue')
}2. 过渡动画性能优化
- 优先使用
transform和opacity属性,避免触发重排 - 使用
will-change提示浏览器优化
.fade-enter-active,
.fade-leave-active {
transition:
opacity 0.3s,
transform 0.3s;
will-change: opacity, transform;
}3. keep-alive 缓存策略
- 只缓存需要保留状态的组件
- 使用
max限制缓存数量,避免内存泄漏 - 使用
include/exclude精确控制缓存范围
4. 插槽设计原则
- 为插槽提供合理的默认内容
- 使用具名插槽组织复杂布局
- 作用域插槽用于需要子组件数据的场景
5. 组合使用内置组件
<transition name="fade" mode="out-in">
<keep-alive include="Home,Posts">
<component :is="currentComponent"></component>
</keep-alive>
</transition>