在 Vue 应用中,组件切换是一个非常常见的需求:Tab 页签切换、步骤条前进后退、路由跳转再返回……默认情况下,每当一个组件被切换掉(比如 v-if 变为 false 或者动态组件 <component :is> 切换),它就会被销毁,下次再切回来时,组件会被重新创建,所有内部状态都回到初始值。
这就带来一个实际问题:用户在一个表单里填了一半的信息,切换到另一个标签页看了一眼,再切回来,表单已经空了——因为组件销毁后所有数据都丢失了。
<KeepAlive> 就是用来解决这个问题的。它会缓存被包裹的非活跃组件实例,而不是直接销毁它们。当组件再次被激活时,之前的状态、DOM 结构、滚动位置都会被完整还原。
基础用法
把动态组件或 <router-view> 用 <KeepAlive> 包裹起来即可:
<!-- 动态组件缓存 -->
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
<!-- 路由视图缓存 -->
<KeepAlive>
<router-view />
</KeepAlive>
现在当 currentTab 从 'TabA' 切换到 'TabB' 时,TabA 的组件实例不会被销毁,而是被缓存到内存中。切回 'TabA' 时直接复用缓存实例,所有状态保持原样。
控制哪些组件需要缓存
不是所有组件都适合缓存——比如一个实时数据大屏组件,你希望它每次激活时都重新拉取最新数据,而不是显示三分钟前的旧内容。<KeepAlive> 提供了三个配置来精细控制:
include 和 exclude:通过组件名称(name 选项)来白名单或黑名单缓存。
<!-- 只缓存名为 UserList 和 OrderList 的组件 -->
<KeepAlive :include="['UserList', 'OrderList']">
<component :is="currentTab" />
</KeepAlive>
<!-- 不缓存名为 LiveMonitor 的组件 -->
<KeepAlive :exclude="['LiveMonitor']">
<component :is="currentTab" />
</KeepAlive>
注意:组件必须显式定义 name 选项或 <script setup> 中的命名,否则 include/exclude 无法匹配。
max:限制最大缓存实例数。当缓存数量超过 max 时,最久未访问的组件实例会被销毁,释放内存。
<KeepAlive :max="5">
<component :is="currentTab" />
</KeepAlive>
缓存组件独有的生命周期钩子
被 <KeepAlive> 包裹的组件会额外获得两个生命周期钩子:
activated:组件从缓存中被激活时调用(第一次创建并激活时也会调用)。deactivated:组件被缓存停用时调用(而不是unmounted)。
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 从缓存激活:可以在这里重新拉取最新数据
console.log('组件激活了,可能需要刷新数据')
})
onDeactivated(() => {
// 组件被缓存停用:可以暂停定时器、取消网络请求等
console.log('组件停用了,可以清理临时任务')
})
</script>
这非常实用:你可以在组件缓存时暂停一个轮询定时器,激活时再重新开启,避免后台无意义的网络请求。或者切换到列表详情页再返回时,在 activated 里判断是否需要刷新数据。
工作原理(简单理解)
<KeepAlive> 内部维护了一个缓存池。当包裹的组件被切换走时,它会将组件实例的 VNode 和对应的 DOM 元素移入缓存,而不是调用 unmount 销毁。同时保存该组件的渲染状态、响应式数据、DOM 引用等一切信息。当组件再次被激活时,直接从缓存池取出实例,重新插入到 DOM 树中,并触发 activated 钩子。
因为缓存的只是组件实例(JavaScript 对象)和内存中的 DOM 引用,并没有重新执行创建、挂载等昂贵的初始化过程,所以切换速度极快,几乎是瞬间完成。
适用场景与性能收益
- 表单保持:多步骤表单、筛选面板,切换时不丢失已填写的数据。
- Tab 页签切换:用户在一个页签操作滚动位置或输入内容,切换到另一个页签后返回,一切如初。
- 列表-详情页往返:从商品列表点击进入详情,返回后列表保留之前的滚动位置和分页状态,而不需要重新请求第一页数据。
性能收益主要体现在两方面:
- 切换开销大幅降低:无缓存时,每次切换都要销毁旧组件、创建新组件、执行完整的挂在流程;有缓存时,激活一个已缓存的组件只涉及 DOM 重新插入和钩子调用,耗时通常是微秒级别。
- 减少不必要的网络请求:配合
activated/deactivated实现智能数据刷新策略(比如“仅在数据可能过期时重新请求”),避免每次切换都重新拉取相同数据。
一个容易踩的坑
<KeepAlive> 只会缓存直接子组件,如果你的动态组件或路由视图被嵌套在多层 <div> 或过渡组件里面,需要确保 <KeepAlive> 紧贴需要缓存的组件。例如:
<!-- 错误:KeepAlive 子节点是 <div>,无法缓存组件 -->
<KeepAlive>
<div>
<component :is="tab" />
</div>
</KeepAlive>
<!-- 正确:KeepAlive 直接包裹动态组件 -->
<KeepAlive>
<component :is="tab" />
</KeepAlive>
如果必须在外层加包装,可以让组件成为 <KeepAlive> 的唯一直接子级。
总的来说,<KeepAlive> 是 Vue 中一个成本极低但收益极高的性能优化手段。它用几行标签就解决了“状态保持”和“切换性能”这两个在复杂交互中频繁出现的问题,是一种用好了能显著提升用户体验的内置能力。