目录

一、什么是 KeepAlive?

二、KeepAlive 的核心数据结构

三、KeepAlive 的工作原理(三步走)

第 1 步:挂载时(首次渲染)——“存”

第 2 步:切换离开时(失活)——“停”

第 3 步:切换回来时(激活)——“取”

四、KeepAlive 的“内存管理”机制

五、常被问到的两个面试点

六、实际开发中的避坑指南

1. 配合 include / exclude 按需缓存

2. 务必搭配 max 防止内存泄漏

3. 在 onActivated 中刷新数据,而非 onMounted

4. Vue 2 vs Vue 3 的细微差别

七、总结


一、什么是 KeepAlive?

KeepAlive 是 Vue 内置的一个抽象组件,它的核心作用就像手机上的“后台应用管理”——当你从 A 页面切到 B 页面时,A 页面不会被销毁,而是被“冻住”放在后台。当你再切回来时,页面瞬间恢复,就像从来没有离开过。

<template>
  <!-- ❌ 没有 KeepAlive:切走即销毁,切回重建 -->
  <component :is="currentTab" />

  <!-- ✅ 有 KeepAlive:切走即缓存,切回恢复 -->
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>
</template>

两种模式的对比

场景无 KeepAlive有 KeepAlive
A 切到 BA 执行 unmounted,组件被销毁A 执行 deactivated,组件被缓存
B 切回 AA 重新 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> 第一次渲染它的默认插槽时:

  1. 获取第一个子组件的 VNode。

  2. 生成唯一的 key(优先取组件 name,否则自动生成)。

  3. 检查 cache 中是否已有该 key

    • 没有(首次访问):将当前 VNode 存入 cache.set(key, vnode),同时 keys.push(key)

    • 有(命中缓存):直接取出缓存的 VNode,复用该实例。

  4. 将选中的 VNode 返回给渲染器去挂载。

第 2 步:切换离开时(失活)——“停”

当被包裹的组件切换走时:

  1. KeepAlive 并不会调用 unmount 去销毁它。

  2. 而是调用 deactivate(失活) 函数,将组件实例标记为失活状态。

  3. 触发该组件的 deactivated 生命周期钩子。

  4. 最关键的是:组件实例和对应的真实 DOM 依然保留在内存中,未被移除。

第 3 步:切换回来时(激活)——“取”

当再次切换回该组件时:

  1. KeepAlive 从 cache 中根据 key 取出之前缓存的 VNode。

  2. 该 VNode 仍然挂载着之前的组件实例和 DOM 元素。

  3. 将失活标记取消。

  4. 直接复用这个实例和 DOM 进行渲染,跳过创建和挂载过程。

  5. 触发该组件的 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 2Vue 3
组件名<keep-alive>(全小写)<KeepAlive>(驼峰,模板中两者都支持)
匹配规则基础匹配更严格,支持正则表达式
生态兼容-结合 Suspense / Teleport 兼容性更好

七、总结

KeepAlive 的本质是一个缓存管理器

  • 它不渲染任何 DOM,只管理被包裹组件的 VNode 生命周期

  • 核心是 cache + keys,用 Map 存 VNode,用数组管理顺序

  • LRU 淘汰策略确保内存可控,防止页面越用越卡

  • activated / deactivated 是缓存组件的专属生命周期钩子

一句话记住它:KeepAlive 让组件在切换时“假死”而非“真死”,从而换取极致的返回体验。

(PS:本文由deepseek辅助生成)

更多推荐