Vue 2 提供了一系列内置组件,这些组件无需注册即可在任何组件模板中直接使用。内置组件主要用于实现动态组件、过渡动画、组件缓存和内容分发等核心功能。
| 组件 | 用途 | 是否渲染真实 DOM |
|---|---|---|
component | 动态组件渲染 | 是 |
transition | 单元素/组件过渡动画 | 否(抽象组件) |
transition-group | 多元素/组件过渡动画 | 是 |
keep-alive | 组件实例缓存 | 否(抽象组件) |
slot | 内容分发插槽 | 否(插槽占位符) |
<component> 是一个动态组件容器,根据 is 属性的值动态渲染不同的组件。它允许在运行时决定渲染哪个组件,是实现组件动态切换的核心方式。
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
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> 为单个元素或组件提供进入/离开过渡动画效果。它是一个抽象组件,不会渲染真实的 DOM 元素,只负责管理过渡动画的生命周期。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
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 | 初始渲染过渡被取消时触发 |
过渡过程中,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:新元素先进入,旧元素等待新元素进入后再离开<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> 不同,它会渲染一个真实的 DOM 元素,并支持列表项的移动动画。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
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">{{ 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() {
// 需先引入 lodash(_.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 的值必须是唯一且稳定的,避免使用索引作为 keytransform 属性,确保元素的 position 样式允许移动<keep-alive> 用于缓存不活动的组件实例,避免组件切换时被销毁和重新创建。它是一个抽象组件,不会渲染真实的 DOM 元素。被缓存的组件会触发 activated 和 deactivated 生命周期钩子。
| 属性 | 类型 | 说明 |
|---|---|---|
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 name="fade" mode="out-in">
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</transition>支持多种匹配方式:
<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> 标签所在的位置。
| 属性 | 类型 | 说明 |
|---|---|---|
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: "低" }
]
}
}
})<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>使用 <keep-alive> 包裹 <component>:
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>检查以下几点:
name-enter、name-enter-active 等)v-if、v-show 或动态 keytransition 或 animation 属性是否正确设置确保:
key(不要使用索引).name-move 类display: inline-block)使用 activated 钩子在组件激活时刷新数据:
export default {
activated() {
this.fetchData()
}
}使用 :key 强制重新创建:
<keep-alive>
<component :is="currentComponent" :key="componentKey"></component>
</keep-alive>data() {
return {
componentKey: 0
}
},
methods: {
forceRefresh() {
this.componentKey++
}
}使用语义化的组件名称,便于调试和维护:
components: {
UserProfile: () => import('./UserProfile.vue'),
UserSettings: () => import('./UserSettings.vue'),
UserActivity: () => import('./UserActivity.vue')
}transform 和 opacity 属性,避免触发重排will-change 提示浏览器优化.fade-enter-active,
.fade-leave-active {
transition:
opacity 0.3s,
transform 0.3s;
will-change: opacity, transform;
}max 限制缓存数量,避免内存泄漏include/exclude 精确控制缓存范围<transition name="fade" mode="out-in">
<keep-alive include="Home,Posts">
<component :is="currentComponent"></component>
</keep-alive>
</transition>