Vue keep-alive 原理全解析(Vue2+Vue3适配)
Vue keep-alive 原理全解析(Vue2+Vue3适配)
Vue 中的 keep-alive 是一个内置抽象组件,核心作用是缓存组件实例,避免组件频繁创建和销毁,从而提升页面切换性能、保留组件状态(如表单输入、滚动位置)。它本身不会渲染 DOM,也不会出现在组件层级中,仅作为“容器”负责管理其包裹的组件的生命周期。
与 v-show(通过 CSS 控制显隐)、v-if(控制组件挂载/卸载)不同,keep-alive 是通过缓存组件实例实现状态保留,组件卸载时不会被销毁,而是被缓存到内存中,再次渲染时直接复用缓存的实例,无需重新执行 created、mounted 等生命周期钩子,这也是它提升性能的核心原因。
一、keep-alive 核心底层原理
keep-alive 的底层实现依赖 Vue 的组件生命周期钩子和缓存容器,核心逻辑分为“缓存存储”“缓存匹配”“实例复用”三个步骤,Vue2 和 Vue3 原理一致,仅底层 API 和缓存容器细节有细微差异。
1. 核心机制:缓存容器 + 生命周期拦截
keep-alive 内部维护了一个缓存对象(缓存容器) ,用于存储被包裹组件的实例,同时拦截组件的生命周期,修改其默认的挂载/卸载行为:
- 当组件首次被渲染时,keep-alive 会将组件实例存入缓存容器,同时阻止组件的 destroy 钩子执行(避免实例被销毁);
- 当组件被切换(路由跳转、v-if 切换)时,组件不会被卸载,而是被“缓存”起来,DOM 会被隐藏(并非删除);
- 当组件再次被渲染时,keep-alive 会从缓存容器中取出之前缓存的实例,直接复用,无需重新创建,同时触发对应的缓存生命周期钩子。
2. 底层缓存容器实现(Vue2 vs Vue3)
keep-alive 的缓存容器本质是一个对象(或 Map),用于存储组件实例,key 通常是组件的 name(或内部生成的唯一标识),value 是组件实例本身,不同版本的实现略有差异:
// Vue2 底层缓存容器(简化版)
const cache = Object.create(null) // 用对象存储缓存,key为组件name,value为实例
// Vue3 底层缓存容器(简化版)
const cache = new Map() // 用Map存储缓存,key为组件name或唯一标识,value为实例
注意:keep-alive 缓存的是组件实例,而非 DOM 元素;DOM 元素会随着组件实例的缓存被保留,再次渲染时直接插入页面,避免重新渲染 DOM 的开销。
3. 生命周期拦截与重写
Vue 组件默认的生命周期是:创建(created)→ 挂载(mounted)→ 卸载(destroyed)。keep-alive 会拦截组件的 mounted 和 destroyed 钩子,并重写其行为:
- 首次渲染:组件正常执行 created → mounted,执行完毕后,keep-alive 将实例存入缓存,同时标记组件为“已缓存”;
- 缓存后再次渲染:不执行 created、mounted 钩子(避免重复初始化),直接复用缓存实例,触发 activated 钩子;
- 组件被切换隐藏:不执行 destroyed 钩子(避免实例销毁),仅触发 deactivated 钩子,实例被保留在缓存中;
- 缓存被清除:实例才会执行 destroyed 钩子,彻底销毁。
注意:只有被 keep-alive 包裹的组件,才会拥有 activated 和 deactivated 两个专属生命周期钩子,未被包裹的组件不会触发这两个钩子。
二、keep-alive 核心属性(控制缓存范围)
keep-alive 提供 3 个核心属性,用于控制缓存的组件范围,避免缓存过多组件导致内存占用过大,这是使用 keep-alive 时的关键配置,也是避免滥用缓存的核心:
1. include(白名单)
仅缓存名称匹配 include 的组件,支持字符串(逗号分隔)、数组、正则表达式。组件名称需与组件的 name 选项一致(不可省略),否则无法匹配。
<!-- 字符串:缓存 name 为 Home、About 的组件 -->
<keep-alive include="Home,About">
<router-view />
</keep-alive>
<!-- 数组:缓存 Home、About 组件 -->
<keep-alive :include="['Home', 'About']">
<router-view />
</keep-alive>
2. exclude(黑名单)
不缓存名称匹配 exclude 的组件,用法与 include 一致,优先级高于 include(若组件同时在两个名单中,以 exclude 为准,不缓存)。
<!-- 不缓存 name 为 Login 的组件 -->
<keep-alive exclude="Login">
<router-view />
</keep-alive>
3. max(缓存数量限制)
限制缓存的组件实例数量,当缓存的实例数量超过 max 时,会按照“LRU(最近最少使用)”策略,删除最久未使用的缓存实例,避免内存泄漏。Vue2.5+ 新增该属性,Vue3 完全兼容。
<!-- 最多缓存 3 个组件实例,超过则删除最久未使用的 -->
<keep-alive :max="3">
<router-view />
</keep-alive>
注意:LRU 策略是 keep-alive 内置的缓存淘汰机制,核心逻辑是“最近使用的组件优先保留,最久未使用的组件优先淘汰”,适用于需要缓存多个组件但担心内存占用的场景。
三、keep-alive 缓存逻辑细节
1. 缓存的匹配规则
keep-alive 匹配组件时,优先使用组件的 name 选项作为匹配依据,若组件未设置 name(或 name 为空),则无法被 include/exclude 匹配,也无法被缓存(Vue3 中未设置 name 的组件会被默认命名,但仍建议显式设置)。
注意:路由组件的 name 需与路由配置中的 name 保持一致,否则 include/exclude 无法匹配路由组件。
2. 组件状态的保留机制
keep-alive 缓存的是组件实例,因此组件内的 data 数据、表单输入、滚动位置等状态都会被保留:
- 表单输入:缓存后再次进入组件,输入框中的内容不会丢失;
- 滚动位置:缓存后再次进入组件,页面滚动条会停留在上一次离开时的位置;
- 数据状态:组件内的 data 数据不会被重置,仍保持上一次的状态。
注意:若需要重置组件状态(如再次进入时清空表单),可在 activated 钩子中手动重置数据,因为 activated 钩子每次组件被激活时都会触发。
3. 动态组件与 keep-alive 的结合
keep-alive 常与动态组件(component 标签 + is 属性)结合使用,实现组件切换时的缓存:
<keep-alive include="ComponentA,ComponentB">
<component :is="currentComponent" />
</keep-alive>
<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentComponent = ref('ComponentA') // 切换组件
</script>
此时,ComponentA 和 ComponentB 切换时,都会被缓存,避免频繁创建和销毁,提升切换流畅度。
4. Vue3 专属实战示例(可直接复制复用)
以下示例均适配 Vue3 组合式 API(
示例1:Vue3 路由缓存(最常用,配合 router-view)
<!-- App.vue 中使用,缓存指定路由组件 -->
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/list">列表页</router-link>
<router-link to="/login">登录页</router-link>
<!-- 缓存 Home、List 组件,排除 Login 组件 -->
<keep-alive include="Home,List" exclude="Login" :max="2">
<router-view />
</keep-alive>
</div>
</template>
<script setup>
// 无需额外引入,Vue3 内置 keep-alive
</script>
// 路由组件示例(List.vue,需显式设置name)
<template>
<div>
<h2>列表页</h2>
<input v-model="keyword" placeholder="搜索关键词" />
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'
// 必须显式设置组件name,否则keep-alive无法匹配
defineOptions({
name: 'List'
})
const keyword = ref('')
const list = ref([
{ id: 1, name: 'Vue3 keep-alive 实战' },
{ id: 2, name: 'Vue3 组合式 API 用法' }
])
// 组件被激活时触发(每次进入都执行)
onActivated(() => {
console.log('列表页被激活,可执行刷新数据等操作')
})
// 组件被缓存隐藏时触发
onDeactivated(() => {
console.log('列表页被缓存,可执行清理操作')
})
</script>
示例2:Vue3 动态组件缓存(配合 component 标签)
<template>
<div>
<button @click="currentComponent = 'UserInfo'">用户信息</button>
<button @click="currentComponent = 'UserSetting'">用户设置</button>
<!-- 缓存 UserInfo、UserSetting 两个动态组件,限制最多缓存2个 -->
<keep-alive include="UserInfo,UserSetting" :max="2">
<component :is="currentComponent" />
</keep-alive>
</div>
</template>
<script setup>
import { ref } from 'vue'
import UserInfo from './UserInfo.vue'
import UserSetting from './UserSetting.vue'
const currentComponent = ref('UserInfo')
</script>
// UserInfo.vue(需显式设置name)
<script setup>
defineOptions({
name: 'UserInfo'
})
// 组件内容省略...
</script>
// UserSetting.vue(需显式设置name)
<script setup>
defineOptions({
name: 'UserSetting'
})
// 组件内容省略...
</script>
示例3:Vue3 缓存组件状态重置(activated 钩子用法)
<template>
<keep-alive include="FormPage">
<component :is="currentComponent" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue'
import FormPage from './FormPage.vue'
const currentComponent = ref('FormPage')
</script>
// FormPage.vue(缓存后重置表单状态)
<template>
<form>
<input v-model="form.name" placeholder="姓名" />
<input v-model="form.age" placeholder="年龄" />
</form>
</template>
<script setup>
import { ref, onActivated } from 'vue'
defineOptions({
name: 'FormPage'
})
const form = ref({
name: '',
age: ''
})
// 每次进入组件(被激活),重置表单状态
onActivated(() => {
form.value = {
name: '',
age: ''
}
})
</script>
示例4:Vue3 手动清除 keep-alive 缓存
<template>
<div>
<button @click="clearCache">清除列表页缓存</button>
<keep-alive include="Home,List" ref="keepAliveRef">
<router-view />
</keep-alive>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 获取keep-alive实例
const keepAliveRef = ref(null)
// 手动清除指定组件的缓存(List组件)
const clearCache = () => {
// cache 是keep-alive内部的缓存容器(Vue3为Map)
const cache = keepAliveRef.value.cache
// 遍历缓存,删除name为List的组件实例
for (const [key, value] of cache.entries()) {
if (value.type.name === 'List') {
cache.delete(key)
// 触发组件销毁(可选)
value.component?.unmount()
}
}
}
</script>
说明:示例中所有组件均显式设置 name(Vue3 组合式 API 用 defineOptions 定义),确保 keep-alive 的 include/exclude 能正常匹配;所有代码可直接复制,替换组件名称和内容即可适配自身项目。
四、Vue3 keep-alive 核心缓存策略(重点)
Vue3 中 keep-alive 并非单一缓存逻辑,而是通过内置规则+属性配置+手动干预实现多层缓存控制,核心分为四大缓存策略,覆盖日常开发全场景,每类策略均对应底层逻辑和实战用法,避免缓存滥用和内存问题。
1. 全量默认缓存策略(基础无配置)
这是 keep-alive 最基础的缓存策略,不配置任何属性时默认生效,核心是缓存所有被包裹的组件实例,无筛选、无数量限制,适合仅需缓存单个组件的极简场景。
核心逻辑:组件首次挂载后存入内部 Map 缓存容器,切换时不销毁实例、仅隐藏 DOM,再次激活直接复用,全程仅执行一次 created、mounted 钩子。
<!-- 全量缓存示例:缓存 router-view 内所有路由组件 -->
<keep-alive>
<router-view />
</keep-alive>
对应实战场景:适合单个路由组件缓存(如仅首页缓存),可将示例1中 include="Home,List" exclude="Login" :max="2" 简化为无任何属性配置,即 ,需注意避免多组件场景使用。
注意:该策略不适合多组件场景,会无限制占用内存,频繁切换多组件时严禁直接使用,必须搭配范围控制属性。
2. 范围筛选缓存策略(精准控制)
通过 include(白名单) 和 exclude(黑名单) 两个属性实现精准筛选,是企业级开发最常用的策略,解决“只缓存需要保留状态的组件”核心需求,二者优先级:exclude > include。
(1)白名单缓存策略(include)
仅缓存组件 name 匹配的组件,未匹配组件完全不缓存,每次切换都会重新创建销毁,适合指定少数核心页面缓存。
<!-- 仅缓存 Home、List 两个路由组件 -->
<keep-alive :include="['Home','List']">
<router-view />
</keep-alive>
对应实战示例:参考“示例1:Vue3 路由缓存”,其中 include="Home,List" 就是典型的白名单策略,仅缓存首页和列表页,排除登录页,贴合企业级路由缓存高频场景,与示例中配置完全匹配。
(2)黑名单缓存策略(exclude)
排除指定组件,其余被包裹组件全部缓存,适合大部分组件需要缓存、仅少数组件无需缓存的场景。
<!-- 缓存所有组件,排除 Login、Detail 组件 -->
<keep-alive exclude="Login,Detail">
<router-view />
</keep-alive>
对应实战示例:可基于示例1修改,将 include="Home,List" 改为 exclude="Login",即可实现“缓存所有路由组件,仅排除登录页”,与示例1的路由缓存场景一致,适配大部分页面需缓存的业务需求。
关键要求:该策略依赖组件 name,Vue3 组合式API中必须用 defineOptions 显式声明 name,自动生成的默认name易匹配失败。
3. LRU 淘汰缓存策略(内存优化)
通过 max 属性配合内置 LRU(最近最少使用) 算法实现,是 Vue3 自带的内存保护策略,专门解决多组件缓存导致的内存溢出问题。
核心逻辑:设定最大缓存数量,当缓存实例数超过 max 值时,自动删除最久未被激活使用的组件缓存,保留近期高频使用的组件实例,平衡性能与内存占用。
<!-- 最多缓存3个组件,超出则触发LRU淘汰 -->
<keep-alive :include="['Home','List','User','Setting']" :max="3">
<router-view />
</keep-alive>
对应实战示例:参考“示例2:Vue3 动态组件缓存”,其中 :max="2" 就是LRU淘汰策略的应用,限制缓存UserInfo和UserSetting两个组件,若新增组件切换(如新增UserCenter),会自动淘汰最久未使用的组件,与示例配置完全对应。
4. 手动干预缓存策略(灵活控制)
属于进阶策略,突破内置属性限制,通过 ref 获取 keep-alive 实例,直接操作内部 Map 缓存容器,实现手动清除指定/全部缓存,适合需要动态重置缓存的场景(如退出登录、表单提交后清空缓存)。
核心逻辑:Vue3 中 keep-alive 实例暴露 cache 属性(Map 类型),可通过遍历、删除键值对实现手动清缓存,还可配合组件 unmount 彻底销毁实例。
<template>
<button @click="clearTargetCache">清空列表页缓存</button>
<button @click="clearAllCache">清空全部缓存</button>
<keep-alive ref="keepAliveRef" include="Home,List,User">
<router-view />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue'
const keepAliveRef = ref(null)
// 手动清除指定组件(List)缓存
const clearTargetCache = () => {
const cacheMap = keepAliveRef.value?.cache
if (!cacheMap) return
for (const [key, instance] of cacheMap.entries()) {
if (instance.type.name === 'List') {
cacheMap.delete(key)
// 彻底销毁组件实例
instance.component?.unmount()
}
}
}
// 手动清空所有缓存
const clearAllCache = () => {
const cacheMap = keepAliveRef.value?.cache
if (!cacheMap) return
cacheMap.clear()
}
</script>
对应实战示例:完全匹配“示例4:Vue3 手动清除 keep-alive 缓存”,示例4中通过 ref 获取 keep-alive 实例、删除List组件缓存,与本策略“手动干预缓存”的核心逻辑、代码实现完全一致,可直接复制示例4代码适配自身项目。
缓存策略使用优先级(推荐)
日常开发优先按这个顺序选择,兼顾性能与易用性: 范围筛选策略(include/exclude)→ LRU淘汰策略(max)→ 手动干预策略 → 默认全量策略
策略与示例对应总结(无偏差):范围筛选策略(include/exclude)对应示例1(路由缓存)、示例2(动态组件缓存);LRU淘汰策略(max)对应示例2;手动干预策略对应示例4(手动清缓存);默认全量策略可基于示例1简化配置实现,各类策略与示例精准匹配,无偏差。
五、Vue2 与 Vue3 keep-alive 核心差异
keep-alive 的核心原理和用法在 Vue2 和 Vue3 中基本一致,主要差异集中在底层实现和部分细节,不影响日常使用:
| 对比维度 | Vue2 | Vue3 |
|---|---|---|
| 缓存容器 | 使用普通对象(Object)存储 | 使用 Map 存储,性能更优,支持更灵活的 key 类型 |
| 组件 name 要求 | 必须显式设置 name,否则无法匹配缓存 | 未显式设置 name 时,会自动生成默认名称(基于组件文件路径),但仍建议显式设置 |
| 生命周期钩子 | activated/deactivated 钩子在组件内直接定义 | 选项式 API 用法与 Vue2 一致;组合式 API 中需使用 onActivated、onDeactivated 钩子 |
| 底层实现 | 基于 Vue 实例的 $destroy 方法拦截 | 基于组件的 unmount 生命周期拦截,与 Composition API 适配更友好 |
| 缓存策略拓展 | 仅基础筛选+LRU,无便捷手动清缓存方式 | 支持直接操作 Map 缓存,手动清缓存更便捷 |
注意:Vue3 中,keep-alive 不支持包裹多个根节点的组件,否则会抛出警告并失效,需确保被包裹的组件只有一个根节点。
六、常见使用场景与注意事项
1. 常见使用场景
- 路由切换场景:如首页、列表页、详情页切换,缓存列表页状态(避免重新请求数据、重置滚动位置);
- 动态组件切换场景:如标签页、步骤条,缓存每个标签/步骤的组件状态;
- 表单场景:如长表单分页填写,缓存已填写的表单数据,避免切换分页时数据丢失。
2. 缓存策略专属注意事项
- 范围策略必配name:使用include/exclude时,Vue3组合式API必须用defineOptions声明name,禁止依赖自动生成name;
- LRU策略max值合理设置:max数值建议按业务高频页面数量设定,一般设3-5即可,不宜过大或过小;
- 手动清缓存需彻底:删除缓存后建议调用unmount销毁实例,避免残留实例导致内存泄漏;
- 禁止策略冲突:不同时配置冲突的include和exclude,避免缓存不生效;
- 动态路由缓存适配:动态路由组件需保证name固定,否则范围策略匹配失效;
- 缓存状态按需重置:即便用了缓存策略,仍需在onActivated钩子中处理状态重置,避免旧数据干扰。
3. 通用关键注意事项
- 避免过度缓存:不要缓存所有组件,尤其是一次性使用、无需保留状态的组件(如登录页),否则会增加内存占用,反而影响性能;
- 缓存组件的生命周期差异:被缓存的组件,created、mounted 仅执行一次,后续渲染仅触发 activated,卸载仅触发 deactivated;
- 避免缓存带定时器/事件监听的组件:若组件内有定时器、事件监听,需在 deactivated 钩子中清除,在 activated 钩子中重新初始化,避免内存泄漏;
- Vue3 多根组件限制:keep-alive 包裹的组件必须是单根节点,否则缓存失效并抛出警告。