在 Vue 3 单页应用里,组件切换时默认会被销毁并重新创建,这会导致列表滚动位置、表单输入、分页状态等丢失。keep-alive 的作用就是在组件切换时把不活动的组件实例缓存起来,而不是直接销毁。它常用于动态组件和路由视图,但仅靠默认的全局缓存往往不够精细,缓存太多组件还会占用内存,include、exclude、max 三个属性正是为了精确控制缓存范围与上限而设计的。接下来会从匹配规则到实战组合逐一说明。

一、keep-alive 的缓存机制与匹配原理
要理解 include、exclude 和 max,先要明白 keep-alive 内部做了什么。默认情况下,Vue 在切换动态组件时会卸载旧组件实例,触发 unmounted 生命周期,组件内的所有响应式数据、DOM 状态都会被清理。而使用 <keep-alive> 包裹后,组件不会被真正卸载,而是被移入一个缓存对象中,页面上的 DOM 节点也被保留,只是切换时不再显示。此时组件会触发 deactivated 生命周期,而不是 unmounted;当切换回来时会触发 activated。这两个生命周期对于列表页恢复滚动位置、暂停轮询请求等场景非常有用。
include 和 exclude 控制的是哪些组件可以进入这个缓存对象。它们匹配的不是路由配置里的 name,也不是组件文件名,而是组件定义中的 name 选项。Vue 3 中如果使用普通选项式 API,可以直接在组件里写 name: 'HomePage';如果使用 <script setup>,由于默认无法直接声明 name,需要借助 defineOptions 来显式指定,例如 defineOptions({ name: 'HomePage' })。很多项目里缓存不生效,往往就是因为组件没有声明 name,导致 include 匹配不到目标。
缓存时 Vue 会以组件 name 作为 key 保存实例。同一个组件在不同路由下如果复用同一个 name,也会共享缓存实例,这通常不是期望的行为,因此每个需要独立缓存的页面都应该有唯一的 name。下面是一个基础示例:
<template>
<keep-alive include="HomePage,ListPage">
<component :is="currentTab" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue';
import HomePage from './HomePage.vue';
import ListPage from './ListPage.vue';
const currentTab = ref('HomePage');
</script>
二、include 和 exclude 的写法与优先级
include 和 exclude 都支持三种数据类型:字符串、正则表达式和数组。字符串形式使用逗号分隔多个组件名,例如 include="HomePage,ListPage";正则形式直接传入一个正则对象,例如 :include="/^Page/",表示匹配所有以 Page 开头的组件 name;数组形式则列出明确的 name 字符串,例如 :include="['HomePage', 'ListPage']"。在实际项目中,数组形式最常用,因为它便于从路由配置中动态生成。
当 include 和 exclude 同时出现并产生冲突时,exclude 的优先级更高。也就是说,如果一个组件既被 include 包含又被 exclude 排除,最终该组件不会被缓存。这个规则适合做全局排除,比如所有页面都缓存,但登录页、注册页、404 页等不想保留状态,就可以使用 exclude 来剔除。另外 exclude 也可以单独使用,不设置 include 时表示除 exclude 匹配项之外的组件都缓存。
include 和 exclude 是响应式的,动态修改它们会立即影响缓存。当某个组件不再满足 include 条件或开始满足 exclude 条件时,它对应的缓存实例会被立即销毁,触发 unmounted。这在使用权限控制或动态标签页时非常有用,可以避免敏感数据长时间留在内存中。下面是一个动态 include 的示例:
<template>
<keep-alive :include="cachedPages">
<router-view />
</keep-alive>
</template>
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const cachedPages = computed(() => {
// 根据路由 meta.keepAlive 动态生成需要缓存的组件名
return route.matched
.filter(r => r.meta?.keepAlive)
.map(r => r.components?.default?.name)
.filter(Boolean);
});
</script>
三、max 属性如何限制缓存数量与淘汰策略
max 属性接收一个数字,用于限制 keep-alive 最多缓存多少个组件实例。如果不设置 max,缓存数量不受限制,在一个包含大量页面的应用里,内存占用会持续增长。设置一个合理的 max 值可以在保留常用页面状态的同时,防止内存过度膨胀。比如后台管理系统中经常切换的页面可能只有十几个,设置 :max="10" 就能覆盖大多数使用场景。
当缓存实例数量达到 max 上限后,再有新的组件需要缓存时,keep-alive 会淘汰最久没有被访问的组件实例。这种策略类似于 LRU(Least Recently Used,最近最少使用)。具体表现为:每次组件被激活时,它在缓存队列中的位置会被移到最新;而一直闲置的组件会逐渐被挤到队尾。超过 max 后,队尾的组件实例会被销毁。这个销毁过程是完整的,会触发该组件的 unmounted 生命周期,相关状态也随之释放。
需要注意一点:max 与 include、exclude 叠加时,max 的淘汰范围是当前已经缓存的组件。即使某个组件在 include 列表中,如果它长期没有被访问,而缓存数量又达到了 max,它依然会被淘汰。反过来,被 exclude 排除的组件根本不会进入缓存,不会占用 max 名额。因此,在设置 include 时不要以为只要写进去就会一直缓存,还要结合 max 和实际访问频率来考虑。下面展示 max 的使用:
<template>
<keep-alive :include="['HomePage', 'ListPage', 'DetailPage']" :max="2">
<component :is="currentView" />
</keep-alive>
</template>
在上面的例子中,虽然 include 允许缓存三个组件,但 max 限制为 2,所以最多只能同时保留两个实例。如果用户依次打开 HomePage、ListPage、DetailPage,HomePage 会被淘汰,因为它是前两个中最久未访问的。
四、组合使用与常见问题
把 include、exclude、max 结合起来,可以设计一套分层缓存方案。比如全站页面默认通过 <keep-alive> 包裹,然后使用 exclude 排除登录、注册、404 等不需要缓存的页面;通过路由 meta 动态生成 include 列表,只缓存核心业务页面;最后设置一个 max 值,比如 15 或 20,来兜底控制内存。这种方式既能保证用户返回列表页时状态还在,又不会因为缓存过多页面导致性能问题。
实际操作中有几个常见的坑需要注意。第一是组件 name 与路由 name 的混淆。路由配置里的 name 是路由名称,include/exclude 匹配的是组件内的 name 选项,两者可能相同也可能不同,不要混用。第二是动态组件切换时,如果组件没有声明 name,include 会直接忽略它,导致缓存不生效,排查时可以先打印组件实例的 $.type.name 确认。第三是 include 数组动态变化后,被移除的组件缓存会被销毁,之前保留的表单数据会丢失,因此不要频繁修改 include,最好基于稳定的业务模块来划分。
还有一个容易忽略的点:keep-alive 包裹的是 <router-view> 或 <component>,如果组件内部还有嵌套的 keep-alive,外层的 max 和内层缓存会相互影响。一般情况下不建议多层嵌套 keep-alive,除非有明确的父子页面需要独立缓存。合理规划 include、exclude 和 max,可以让 Vue 3 应用的页面状态管理既灵活又可控。
Vue 3 keep-alive组件缓存include exclude max修改时间:2026-09-29 09:45:49