动态组件让你可以在同一个挂载点动态切换不同的组件。这在实现标签页、多步骤表单、动态内容加载等场景非常有用。
使用 <component> 元素和 is 属性动态切换组件:
<div id="app">
<button @click="current = 'home'">首页</button>
<button @click="current = 'posts'">文章</button>
<button @click="current = 'archive'">归档</button>
<component :is="current"></component>
</div>
<script>
Vue.component('home', {
template: '<div>首页内容</div>'
})
Vue.component('posts', {
template: '<div>文章列表</div>'
})
Vue.component('archive', {
template: '<div>归档内容</div>'
})
new Vue({
el: '#app',
data: {
current: 'home'
}
})
</script>
is 属性可以直接绑定组件对象:
<div id="app">
<button
v-for="tab in tabs"
:key="tab.name"
@click="currentTab = tab.component"
>
{{ tab.name }}
</button>
<component :is="currentTab"></component>
</div>
<script>
new Vue({
el: '#app',
data: {
currentTab: null,
tabs: [
{
name: '首页',
component: {
template: '<div>首页内容</div>'
}
},
{
name: '文章',
component: {
template: '<div>文章列表</div>'
}
}
]
},
created() {
this.currentTab = this.tabs[0].component
}
})
</script>
切换组件时,默认会销毁旧组件、创建新组件。使用 <keep-alive> 可以缓存组件状态。
<keep-alive>
<component :is="current"></component>
</keep-alive>
<div id="app">
<button @click="current = 'counter-a'">计数器 A</button>
<button @click="current = 'counter-b'">计数器 B</button>
<!-- 不使用 keep-alive:切换后计数器重置 -->
<component :is="current"></component>
<!-- 使用 keep-alive:切换后计数器保持 -->
<keep-alive>
<component :is="current"></component>
</keep-alive>
</div>
<script>
Vue.component('counter-a', {
template: '<div>计数器 A: {{ count }} <button @click="count++">+1</button></div>',
data() {
return { count: 0 }
}
})
Vue.component('counter-b', {
template: '<div>计数器 B: {{ count }} <button @click="count++">+1</button></div>',
data() {
return { count: 0 }
}
})
</script>
指定哪些组件需要缓存:
<!-- 包含指定组件 -->
<keep-alive include="counter-a,counter-b">
<component :is="current"></component>
</keep-alive>
<!-- 排除指定组件 -->
<keep-alive exclude="counter-c">
<component :is="current"></component>
</keep-alive>
<!-- 使用正则 -->
<keep-alive :include="/^counter-/">
<component :is="current"></component>
</keep-alive>
<!-- 使用数组 -->
<keep-alive :include="['counter-a', 'counter-b']">
<component :is="current"></component>
</keep-alive>
限制最多缓存多少组件实例:
<keep-alive :max="10">
<component :is="current"></component>
</keep-alive>
超出限制后,会销毁最久没有访问的实例。
被 keep-alive 缓存的组件有两个特殊的生命周期钩子:
组件被激活时调用:
Vue.component('my-component', {
template: '<div>组件内容</div>',
activated() {
console.log('组件被激活')
}
})
组件被停用时调用:
Vue.component('my-component', {
template: '<div>组件内容</div>',
deactivated() {
console.log('组件被停用')
}
})
Vue.component('user-list', {
template: `
<div>
<h3>用户列表</h3>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
`,
data() {
return {
users: []
}
},
created() {
console.log('created: 初始化')
this.fetchUsers()
},
activated() {
console.log('activated: 组件激活')
// 可以在这里刷新数据
if (this.users.length > 0) {
this.fetchUsers()
}
},
deactivated() {
console.log('deactivated: 组件停用')
},
methods: {
fetchUsers() {
// 模拟 API 调用
console.log('获取用户列表')
}
}
})
<div id="app">
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.name"
:class="{ active: currentTab === tab.name }"
@click="currentTab = tab.name"
>
{{ tab.label }}
</button>
</div>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</div>
<script>
Vue.component('tab-home', {
template: '<div class="tab-pane">首页内容</div>'
})
Vue.component('tab-posts', {
template: '<div class="tab-pane">文章列表</div>'
})
Vue.component('tab-archive', {
template: '<div class="tab-pane">归档内容</div>'
})
new Vue({
el: '#app',
data: {
currentTab: 'home',
tabs: [
{ name: 'home', label: '首页' },
{ name: 'posts', label: '文章' },
{ name: 'archive', label: '归档' }
]
},
computed: {
currentComponent() {
return 'tab-' + this.currentTab
}
}
})
</script>
<div id="app">
<div class="step-indicator">
<span
v-for="(step, index) in steps"
:key="index"
:class="{ active: currentStep >= index, current: currentStep === index }"
>
{{ step.title }}
</span>
</div>
<keep-alive>
<component
:is="steps[currentStep].component"
:data="formData"
@next="nextStep"
@prev="prevStep"
></component>
</keep-alive>
<div class="step-actions">
<button
v-if="currentStep > 0"
@click="prevStep"
>上一步</button>
<button
v-if="currentStep < steps.length - 1"
@click="nextStep"
>下一步</button>
<button
v-if="currentStep === steps.length - 1"
@click="submit"
>提交</button>
</div>
</div>
<script>
Vue.component('step-basic', {
props: ['data'],
template: `
<div class="step-content">
<h3>基本信息</h3>
<input v-model="data.name" placeholder="姓名">
<input v-model="data.email" placeholder="邮箱">
</div>
`
})
Vue.component('step-detail', {
props: ['data'],
template: `
<div class="step-content">
<h3>详细信息</h3>
<input v-model="data.phone" placeholder="电话">
<input v-model="data.address" placeholder="地址">
</div>
`
})
Vue.component('step-confirm', {
props: ['data'],
template: `
<div class="step-content">
<h3>确认信息</h3>
<p>姓名:{{ data.name }}</p>
<p>邮箱:{{ data.email }}</p>
<p>电话:{{ data.phone }}</p>
<p>地址:{{ data.address }}</p>
</div>
`
})
new Vue({
el: '#app',
data: {
currentStep: 0,
formData: {
name: '',
email: '',
phone: '',
address: ''
},
steps: [
{ title: '基本信息', component: 'step-basic' },
{ title: '详细信息', component: 'step-detail' },
{ title: '确认提交', component: 'step-confirm' }
]
},
methods: {
nextStep() {
if (this.currentStep < this.steps.length - 1) {
this.currentStep++
}
},
prevStep() {
if (this.currentStep > 0) {
this.currentStep--
}
},
submit() {
console.log('提交数据:', this.formData)
}
}
})
</script>
<div id="app">
<select v-model="selectedComponent">
<option value="">请选择组件</option>
<option value="chart-bar">柱状图</option>
<option value="chart-line">折线图</option>
<option value="chart-pie">饼图</option>
</select>
<keep-alive>
<component
v-if="selectedComponent"
:is="selectedComponent"
:data="chartData"
></component>
</keep-alive>
</div>
<script>
Vue.component('chart-bar', {
props: ['data'],
template: '<div class="chart">柱状图:{{ data }}</div>'
})
Vue.component('chart-line', {
props: ['data'],
template: '<div class="chart">折线图:{{ data }}</div>'
})
Vue.component('chart-pie', {
props: ['data'],
template: '<div class="chart">饼图:{{ data }}</div>'
})
new Vue({
el: '#app',
data: {
selectedComponent: '',
chartData: [1, 2, 3, 4, 5]
}
})
</script>
<div id="app">
<component :is="currentView"></component>
</div>
<script>
new Vue({
el: '#app',
data: {
user: {
role: 'admin'
}
},
computed: {
currentView() {
if (!this.user) {
return 'login-page'
}
switch (this.user.role) {
case 'admin':
return 'admin-dashboard'
case 'user':
return 'user-dashboard'
default:
return 'guest-page'
}
}
}
})
</script>
keep-alive 要求
keep-alive 要求被包裹的组件必须有 name 属性(用于 include/exclude 匹配):
// ✅ 正确
Vue.component('my-component', {
name: 'MyComponent',
template: '...'
})
// ❌ 可能有问题
Vue.component('my-component', {
template: '...'
})
activated/deactivated vs mounted/destroyed
Vue.component('my-component', {
created() {
console.log('created: 只执行一次')
},
mounted() {
console.log('mounted: 只执行一次')
},
activated() {
console.log('activated: 每次激活都执行')
},
deactivated() {
console.log('deactivated: 每次停用都执行')
}
})
避免内存泄漏
使用 keep-alive 会缓存组件实例,注意避免内存泄漏:
Vue.component('my-component', {
activated() {
// 重新添加事件监听
window.addEventListener('resize', this.handleResize)
},
deactivated() {
// 移除事件监听
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
// ...
}
}
})
动态组件要点:
is 属性动态切换组件至此,Vue2 的组件通信内容已经全部讲解完毕。