Vue.js - 动态组件使用详解(附:配合keep-alive保留组件状态)
作者:hangge | 2026-04-03 08:46
在实际前端开发中,我们经常需要根据用户操作或运行时状态来灵活切换界面内容。例如在标签页(Tabs)切换、表单生成器、配置化界面、插件扩展点等场景中,不同功能块往往对应不同组件。如果只是简单地控制显示/隐藏,很多开发者第一反应是使用 v-if 或 v-show,但这两种方式都会把组件“写死”在模板中:v-if 每次切换会销毁并重建组件实例,而 v-show 虽然不会销毁,但只能控制同一个组件的显示状态,很难在多个组件之间动态切换。
为了让组件能够像数据一样动态切换,Vue 提供了强大的 动态组件(Dynamic Components) 功能,通过 <component :is="..."> 可以让组件的类型由数据控制,从而真正实现“按需渲染”。搭配 keep-alive,我们甚至可以在组件之间来回切换而不丢失状态;配合异步组件,还能实现按需加载,减少首屏体积。下面我将通过样例演示动态组件的使用。
1,基本用法
(1)假设我们需要实现一个通过单击按钮实现切换显示不同的组件(Home、About)的功能。
- 其中 Home.vue 组件代码如下:
<template>
<div class="layout">
Home组件
</div>
</template>
<script>
export default {
name: 'home'
}
</script>
<style scoped>
.layout {
border: 1px solid #ccc;
padding: 20px;
margin: 20px;
}
</style>
- 其中 About.vue 组件代码如下:
<template>
<div class="layout">
About组件
</div>
</template>
<script>
export default {
name: 'about'
}
</script>
<style scoped>
.layout {
border: 1px solid #ccc;
padding: 20px;
margin: 20px;
}
</style>
(2)接着我们使用 Vue.js 内置的 <component> 组件,并通过 is 属性指定动态显示的组件。当 currentTab 为 home 字符串时,显示 <home> 组件。这里的 is 属性用于指定组件的名称, 它支持指定全局或局部组件。
<template>
<div class="app">
<button v-for="item in tabs" @click="itemClick(item)"
:class="{ active: currentTab == item }">
{{ item }}
</button>
<!-- 动态组件的实现(is属性是动态绑定组件的名称。例如is="home”,代表绑定<home/> 组件)-->
<component :is="currentTab"></component>
</div>
</template>
<script>
import Home from './Home.vue';
import About from './About.vue';
export default {
components: {
Home,
About
},
data() {
return {
tabs: ["home", "about"],
currentTab: "home"
}
},
methods: {
itemClick(item) {
this.currentTab = item;
}
}
}
</script>
(3)运行效果如下,默认显示 Home 组件,当单击不同的按钮,会切换显示不同的组件。

2,动态组件的传参
(1)在上述案例中,Home、About 组件都使用 <component> 动态组件进行替代。如果需要向这些组件传递 props 参数或者监听事件,可以直接向动态组件传递这些参数和事件, 这样就会传递到相应的组件中。我们只需要将属性和监听事件放到 <component> 组件中即可。
(2)这里我们修改父组件代码,从而向 Home、About 两个组件都传递 name、age 属性和监听 pageClick 事件。
<template>
<div class="app">
<button v-for="item in tabs" @click="itemClick(item)"
:class="{ active: currentTab == item }">
{{ item }}
</button>
<!-- 动态组件的实现(is属性是动态绑定组件的名称。例如is="home”,代表绑定<home/> 组件)-->
<component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
</component>
</div>
</template>
<script>
import Home from './Home.vue';
import About from './About.vue';
export default {
components: {
Home,
About
},
data() {
return {
tabs: ["home", "about"],
currentTab: "home"
}
},
methods: {
itemClick(item) {
this.currentTab = item;
},
// 监听动态组件的pageClick事件
pageClick(value) {
console.log(value);
}
}
}
</script>
(3)接着修改 Home.vue 组件。为 Home.vue 组件添加 props 属性,用于接收 name 和 age 属性,同时将两个属性显示在 template 中。接着当单击 <div> 元素时,会向父组件触发 pageClick 事件,并传递一个字符串参数。
提示:About.vue 组件也可以接收 name、age 属性和触发 pageClick 事件,这里就不给出代码了。
<template>
<div @click="divClick">
Home组件: {{name}} - {{age}}
</div>
</template>
<script>
export default {
name: "home",
props: {
name: { // 动态组件传递过来的 name
type: String,
default: ""
},
age: {
type: Number,
default: 0
}
},
emits: ["pageClick"], // 动态组件监听的pageClick事件
methods: {
divClick() {
this.$emit("pageClick", 'Home组件触发的点击');
}
}
}
</script>
(4)运行结果如下,可以看到 Home.vue 组件可以正常接收 props,并且其触发的 pageClick 事件也可以被父组件监听到。

附:使用 keep-alive 缓存组件状态
1,keep-alive 的属性介绍
(1)<kep-alive> 组件的属性如下:
- include:支持 string、RegExp、Array 类型。只有名称匹配的组件会被缓存。
- exclude:支持 string、RegExp、Array 类型。任何名称匹配的组件都不会被缓存。
- max:支持 number、string 类型。最多可以缓存多少个组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁。
(2)其中,include 和 exclude 属性允许组件有条件地缓存,二者都可以表示为用逗号分隔的字符串、正则表达式或一个数组。匹配时会首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册的名称。
2,使用方法
(1)默认 <kep-alive> 会缓存其内部的所有组件,假设我们我们仅想缓存 Home.vue 和 About.vue 组件的状态,那么下面三种写法:
<!-- 1.逗号分隔字符串-->
<keep-alive include="home,about">
<component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
</component>
</keep-alive>
<!-- 2.RegExp正则表达式-->
<keep-alive :include="/home|about/">
<component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
</component>
</keep-alive>
<!-- 3.数组形式-->
<keep-alive :include="['home', 'about']">
<component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
</component>
</keep-alive>
(2)这里我们对 Home.vue 组件进行改造,增加一个 <button> 按钮,单击按钮可以实现数字递增的功能。
<template>
<div class="layout">
Home组件:
<button @click="counter++">点击递增:{{ counter }}</button>
</div>
</template>
<script>
export default {
name: 'home',
data() {
return {
counter: 0
}
}
}
</script>
(3)测试一下,我们在 Home.vue 组件下点击按钮将将 counter 递增到 5。然后切换到 About.vue 组件,再切换回 Home.vue 组件时,可以发现计数到 5 的状态可以保留,并没有销毁。

3,缓存组件实例的生命周期
(1)如果一个组件被 keep-alive 包裹,那么它:
- 首次进入时执行 mounted
- 离开时不会销毁,不会执行 destroyed
- 再次进入时会触发 activated
- 离开时触发 deactivated
提示:对缓存组件来说,再次进入时不会再次执行 created 或 mounted 等生命周期函数。但在有些情况下,我们需要监听组件重新进入和离开的时机,这时可以使用 activated 和 deactivated 这两个生命周期钩子函数。
(2)这里我们修改 About.vue 组件,添加 activated 和 deactivated 这两个生命周期函数,代码如下:
<template>
<div class="layout">
About组件
</div>
</template>
<script>
export default {
name: "about",
created() {
console.log("about created");
},
unmounted() {
console.log("about unmounted");
},
// 下面两个是 缓存组件的生命周期
activated() {
console.log("about activated");
},
deactivated() {
console.log("about deactivated");
}
}
</script>
(3)保存代码,在浏览器中依次单击“about”→“home”→“about”按钮可以看到控制台输出内容如下:
- 当单击“about”按钮时,还没有创建 About.vue 组件,这时会先调用 created 函数,然后调用 activated 函数;
- 当单击“home”按钮时,About.vue 组件的 deactivated 函数会回调,unmounted 函数并没有回调,因为组件并没有销毁;
- 当再次单击“about”按钮时, About.vue 组件的 activated 函数又会回调。

全部评论(0)