文章目录
- 前言
- 一、基本用法
-
- [1.1 包裹动态组件](#1.1 包裹动态组件)
- [1.2 配合 router-view](#1.2 配合 router-view)
- 二、生命周期变化
-
- [2.1 首次进入 vs 再次激活](#2.1 首次进入 vs 再次激活)
- [2.2 activated / deactivated 示例](#2.2 activated / deactivated 示例)
- [三、include / exclude / max](#三、include / exclude / max)
-
- [3.1 include:白名单缓存](#3.1 include:白名单缓存)
- [3.2 exclude:黑名单排除](#3.2 exclude:黑名单排除)
- [3.3 max:限制缓存数量(LRU)](#3.3 max:限制缓存数量(LRU))
- [3.4 组件 name 要求](#3.4 组件 name 要求)
- 四、典型场景
-
- [4.1 多 Tab 切换保留状态](#4.1 多 Tab 切换保留状态)
- [4.2 列表页 → 详情页 → 返回](#4.2 列表页 → 详情页 → 返回)
- [4.3 路由 meta 控制缓存](#4.3 路由 meta 控制缓存)
- 五、注意事项
-
- [5.1 只对直接子组件生效](#5.1 只对直接子组件生效)
- [5.2 内存占用](#5.2 内存占用)
- [5.3 activated 与 mounted 的分工](#5.3 activated 与 mounted 的分工)
- 六、面试聚焦
-
- [6.1 列表页 → 详情页 → 返回保持状态](#6.1 列表页 → 详情页 → 返回保持状态)
- [6.2 KeepAlive 缓存会占用内存吗?](#6.2 KeepAlive 缓存会占用内存吗?)
- [6.3 script setup 中如何指定组件名称?](#6.3 script setup 中如何指定组件名称?)
- 七、易混淆点
- 八、思考与练习
- 总结
前言
<KeepAlive> 是 Vue 内置组件,用于缓存动态组件实例,避免反复创建和销毁。切换被缓存的组件时,滚动位置、表单输入等状态得以保留。本篇会讲清楚:
- KeepAlive 的工作原理
activated/deactivated生命周期include/exclude/max缓存策略- 典型应用场景与注意事项
一、基本用法
1.1 包裹动态组件
vue
<template>
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</template>
<script setup>
import { ref, shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'
const tabs = { a: TabA, b: TabB }
const currentTab = shallowRef(TabA)
</script>
没有 KeepAlive :切换 Tab 时旧组件 unmounted、新组件 mounted,状态丢失。
有 KeepAlive :旧组件 deactivated(隐藏但不销毁),新组件 activated,切回时状态仍在。
1.2 配合 router-view
vue
<!-- App.vue -->
<template>
<router-view v-slot="{ Component }">
<KeepAlive>
<component :is="Component" />
</KeepAlive>
</router-view>
</template>
列表页 → 详情页 → 返回列表页时,列表的滚动位置和已加载数据得以保留。
二、生命周期变化
2.1 首次进入 vs 再次激活
首次进入缓存组件:
setup → onBeforeMount → onMounted → onActivated
离开缓存组件(切换到其他):
onDeactivated(不会 onUnmounted)
再次回到缓存组件:
onActivated(不会重新 onMounted)
真正销毁缓存(超出 max 或 include 不匹配):
onDeactivated → onBeforeUnmount → onUnmounted
2.2 activated / deactivated 示例
vue
<script setup>
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('首次挂载,拉取数据')
fetchList()
})
onActivated(() => {
console.log('组件被激活(含首次和再次显示)')
// 从详情页返回时刷新列表(可选)
refreshList()
})
onDeactivated(() => {
console.log('组件被缓存隐藏')
// 暂停定时器、停止监听等
})
onUnmounted(() => {
console.log('缓存被淘汰,真正销毁')
})
</script>
| 钩子 | 触发时机 |
|---|---|
onMounted |
首次挂载(只一次) |
onActivated |
每次显示(含首次) |
onDeactivated |
每次隐藏 |
onUnmounted |
缓存被淘汰时 |
三、include / exclude / max
3.1 include:白名单缓存
vue
<KeepAlive :include="['UserList', 'OrderList']">
<component :is="currentView" />
</KeepAlive>
只有 name 为 UserList 或 OrderList 的组件会被缓存,其余正常销毁。
3.2 exclude:黑名单排除
vue
<KeepAlive :exclude="['Login', 'Register']">
<component :is="currentView" />
</KeepAlive>
除 Login、Register 外,其余组件都缓存。
3.3 max:限制缓存数量(LRU)
vue
<KeepAlive :max="5">
<component :is="currentView" />
</KeepAlive>
最多缓存 5 个组件实例,超出时按 LRU(最近最少使用) 策略淘汰最久未访问的实例。
访问顺序:A → B → C → D → E → F
缓存满(max=5)后访问 F → 淘汰 A(最久未用)
再访问 A → A 重新挂载,可能淘汰 B
3.4 组件 name 要求
include / exclude 基于组件 name 匹配,不是文件名或路由名。
vue
<!-- Options API -->
<script>
export default {
name: 'UserList'
}
</script>
<!-- script setup:需显式声明 -->
<script setup>
defineOptions({ name: 'UserList' })
</script>
<script setup> 中组件名默认取文件名,若与 include 不匹配需用 defineOptions 显式指定。
四、典型场景
4.1 多 Tab 切换保留状态
vue
<template>
<div class="tabs">
<button v-for="tab in tabList" :key="tab.name" @click="current = tab.comp">
{{ tab.label }}
</button>
</div>
<KeepAlive :max="3">
<component :is="current" />
</KeepAlive>
</template>
各 Tab 的表单输入、滚动位置在切换后保留。
4.2 列表页 → 详情页 → 返回
vue
<!-- App.vue -->
<template>
<router-view v-slot="{ Component, route }">
<KeepAlive :include="['UserList', 'OrderList']">
<component :is="Component" :key="route.name" />
</KeepAlive>
</router-view>
</template>
vue
<!-- UserList.vue -->
<script setup>
defineOptions({ name: 'UserList' })
const list = ref([])
const scrollTop = ref(0)
onMounted(() => fetchList())
onActivated(() => {
// 从详情返回时可选择刷新或保持
})
</script>
列表滚动位置、筛选条件、已加载数据在返回时保留,无需重新请求。
4.3 路由 meta 控制缓存
javascript
const routes = [
{ path: '/user', component: UserList, meta: { keepAlive: true } },
{ path: '/user/:id', component: UserDetail, meta: { keepAlive: false } },
{ path: '/order', component: OrderList, meta: { keepAlive: true } }
]
vue
<template>
<router-view v-slot="{ Component, route }">
<KeepAlive>
<component
:is="Component"
v-if="route.meta.keepAlive"
:key="route.name"
/>
</KeepAlive>
<component
:is="Component"
v-if="!route.meta.keepAlive"
:key="route.path"
/>
</router-view>
</template>
按路由 meta 灵活控制哪些页面缓存。
五、注意事项
5.1 只对直接子组件生效
vue
<!-- ✅ 正确:KeepAlive 直接包裹动态组件 -->
<KeepAlive>
<component :is="cur" />
</KeepAlive>
<!-- ❌ 错误:中间有 div 包裹,KeepAlive 无法识别 -->
<KeepAlive>
<div>
<component :is="cur" />
</div>
</KeepAlive>
多层嵌套时,每一层动态组件需各自包裹 KeepAlive。
5.2 内存占用
缓存的组件实例及其 DOM、数据都会保留在内存中。缓存过多会导致内存增长,应:
- 合理设置
max限制数量 - 用
include只缓存必要页面 - 在
onDeactivated中清理定时器、大对象
5.3 activated 与 mounted 的分工
javascript
// onMounted:仅首次挂载执行,适合一次性初始化
onMounted(() => initChart())
// onActivated:每次显示都执行,适合返回时刷新
onActivated(() => refreshData())
需要「每次显示都更新」的逻辑放 onActivated;只需执行一次的放 onMounted。
六、面试聚焦
6.1 列表页 → 详情页 → 返回保持状态
用 <KeepAlive> 包裹 <router-view>,列表组件设置 name 并加入 include。离开列表时触发 deactivated 而非 unmounted,返回时 activated 恢复,滚动位置和数据保留。
6.2 KeepAlive 缓存会占用内存吗?
会 。每个缓存实例包含组件状态、DOM 节点、事件监听等。应通过 max 限制数量,用 include 精确控制,避免无节制缓存。
6.3 script setup 中如何指定组件名称?
javascript
defineOptions({ name: 'UserList' })
include / exclude 匹配的是组件 name,不是文件名。
七、易混淆点
- KeepAlive 不是不销毁 :超出
max或不在include中的组件仍会unmounted。 - include 匹配 name 不是路由名 :需在组件内声明
name或defineOptions。 - onActivated 含首次 :首次进入也会触发,与
onMounted可能同时执行。 - 只对直接子组件生效:中间不能有多余包裹层。
- 缓存 ≠ 数据持久化:刷新页面缓存丢失,需持久化请用 localStorage 或 Pinia 插件。
八、思考与练习
1. KeepAlive 的作用是什么?
解析:缓存动态组件实例,切换时不销毁,保留组件状态(滚动位置、表单输入等)。
2. 被缓存组件切换时触发的生命周期?
解析:离开时 deactivated,再次显示时 activated;不会重复 mounted/unmounted(除非缓存被淘汰)。
3. include 和 exclude 的区别?
解析:include 是白名单,只缓存匹配的;exclude 是黑名单,匹配的不缓存,其余都缓存。
4. max 属性如何工作?
解析:限制最大缓存数,超出时按 LRU 策略淘汰最久未访问的组件实例。
5. 如何让列表页返回时保持滚动位置?
解析:列表组件 defineOptions({ name: 'UserList' }),router-view 外包 KeepAlive 并 include 该 name。
总结
- KeepAlive:缓存动态组件实例,切换时 deactivated/activated 替代 unmounted/mounted
- include / exclude:按组件 name 控制缓存白名单/黑名单
- max + LRU:限制缓存数量,淘汰最久未用的实例
- 典型场景:Tab 切换、列表→详情→返回、多步骤表单
- 注意:组件需声明 name、只对直接子组件生效、合理控制内存