Vue KeepAlive 原理深度解析:从使用到底层实现
目录
3. 在 onActivated 中刷新数据,而非 onMounted
一、什么是 KeepAlive?
KeepAlive 是 Vue 内置的一个抽象组件,它的核心作用就像手机上的“后台应用管理”——当你从 A 页面切到 B 页面时,A 页面不会被销毁,而是被“冻住”放在后台。当你再切回来时,页面瞬间恢复,就像从来没有离开过。
<template>
<!-- ❌ 没有 KeepAlive:切走即销毁,切回重建 -->
<component :is="currentTab" />
<!-- ✅ 有 KeepAlive:切走即缓存,切回恢复 -->
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</template>
两种模式的对比:
| 场景 | 无 KeepAlive | 有 KeepAlive |
|---|---|---|
| A 切到 B | A 执行 unmounted,组件被销毁 | A 执行 deactivated,组件被缓存 |
| B 切回 A | A 重新 mounted,数据重置,页面闪烁 | A 执行 activated,瞬间恢复,状态保留 |
二、KeepAlive 的核心数据结构
在 Vue 3 源码中,KeepAlive 组件内部维护了两个核心变量:
// 伪代码 —— KeepAlive 内部核心数据结构
const cache: Map<string, VNode> = new Map(); // 缓存池:key -> VNode
const keys: string[] = []; // 缓存列表(用于 LRU 淘汰策略)
-
cache:一个Map对象,键是组件的唯一标识(默认用组件的name属性),值是该组件的 VNode(虚拟节点)。VNode 上挂着组件实例(componentInstance)和真实 DOM(el),所以缓存 VNode 就等于缓存了一切。 -
keys:一个数组,按访问顺序存储所有缓存的key,用于实现 LRU(最近最少使用)淘汰算法。
三、KeepAlive 的工作原理(三步走)
第 1 步:挂载时(首次渲染)——“存”
当 <KeepAlive> 第一次渲染它的默认插槽时:
-
获取第一个子组件的 VNode。
-
生成唯一的
key(优先取组件name,否则自动生成)。 -
检查
cache中是否已有该key:-
没有(首次访问):将当前 VNode 存入
cache.set(key, vnode),同时keys.push(key)。 -
有(命中缓存):直接取出缓存的 VNode,复用该实例。
-
-
将选中的 VNode 返回给渲染器去挂载。
第 2 步:切换离开时(失活)——“停”
当被包裹的组件切换走时:
-
KeepAlive并不会调用unmount去销毁它。 -
而是调用
deactivate(失活) 函数,将组件实例标记为失活状态。 -
触发该组件的
deactivated生命周期钩子。 -
最关键的是:组件实例和对应的真实 DOM 依然保留在内存中,未被移除。
第 3 步:切换回来时(激活)——“取”
当再次切换回该组件时:
-
KeepAlive从cache中根据key取出之前缓存的 VNode。 -
该 VNode 仍然挂载着之前的组件实例和 DOM 元素。
-
将失活标记取消。
-
直接复用这个实例和 DOM 进行渲染,跳过创建和挂载过程。
-
触发该组件的
activated生命周期钩子。
四、KeepAlive 的“内存管理”机制
如果 KeepAlive 设置了 max 属性(最大缓存数量),它不会无限累积,而是采用 LRU(Least Recently Used,最近最少使用) 淘汰算法。
淘汰逻辑(源码精简):
// 当缓存数量超过 max 时
if (keys.length > max) {
// 1. 从 keys 数组中移除第一个(最早存入且未被访问的)key
const oldestKey = keys.shift();
// 2. 从 cache 中删除对应的 VNode
cache.delete(oldestKey);
// 3. 如果是组件实例,执行真正的销毁(释放内存)
}
通俗理解:缓存队列就像一个“候车厅的座位”。如果座位满了,最新上车的乘客(最近访问的)坐进来,最久没被叫到名字的乘客(最早缓存的)就要被请出去,把座位让出来。
生命周期对照图:
| 状态 | 有无 KeepAlive | 触发的钩子 |
|---|---|---|
| 组件首次进入 | 无 / 有 | onMounted → onActivated |
| 切走离开 | 无 | onUnmounted(销毁) |
| 切走离开 | 有 | onDeactivated(失活,不销毁) |
| 切回进入 | 无 | onMounted(重建) |
| 切回进入 | 有 | onActivated(复用,不重建) |
| 缓存被 LRU 淘汰(超出 max) | 有 | onUnmounted(真正销毁) |
五、常被问到的两个面试点
Q1:KeepAlive 缓存的是什么?是 DOM 还是数据?
缓存的是 VNode(虚拟节点)+ 组件实例(Component Instance)。组件的 data、computed、methods 都挂在实例上,所以数据、状态、DOM 结构都被完整保留。
Q2:为什么说 KeepAlive 是“抽象组件”?
因为它不渲染任何 DOM 节点,也不出现在父组件的层级关系中。它只是一个逻辑容器,在渲染函数中直接返回被包裹的子组件,自己只充当一个“管理者”的角色。
六、实际开发中的避坑指南
1. 配合 include / exclude 按需缓存
<KeepAlive :include="['Home', 'About']">
<router-view />
</KeepAlive>
只缓存名为 Home 和 About 的组件,其他组件正常销毁。
2. 务必搭配 max 防止内存泄漏
如果路由页面非常多且不加 max 限制,所有访问过的页面都会常驻内存,极易导致移动端白屏或卡顿。
<KeepAlive :max="10">
<router-view />
</KeepAlive>
3. 在 onActivated 中刷新数据,而非 onMounted
<script setup>
import { onActivated } from 'vue';
// ❌ 错误:切回时不会触发 onMounted
onMounted(() => fetchData());
// ✅ 正确:每次激活都会触发
onActivated(() => fetchData());
</script>
4. Vue 2 vs Vue 3 的细微差别
| 对比项 | Vue 2 | Vue 3 |
|---|---|---|
| 组件名 | <keep-alive>(全小写) | <KeepAlive>(驼峰,模板中两者都支持) |
| 匹配规则 | 基础匹配 | 更严格,支持正则表达式 |
| 生态兼容 | - | 结合 Suspense / Teleport 兼容性更好 |
七、总结
KeepAlive 的本质是一个缓存管理器:
-
它不渲染任何 DOM,只管理被包裹组件的 VNode 生命周期
-
核心是
cache+keys,用 Map 存 VNode,用数组管理顺序 -
LRU 淘汰策略确保内存可控,防止页面越用越卡
-
activated/deactivated是缓存组件的专属生命周期钩子
一句话记住它:KeepAlive 让组件在切换时“假死”而非“真死”,从而换取极致的返回体验。
(PS:本文由deepseek辅助生成)
更多推荐
所有评论(0)