【vue篇】Vue 性能优化神器:keep-alive 深度解析与实战指南

在 Vue 开发中,你是否遇到过:

"切换页面时,列表滚动位置丢失?"
"组件反复创建销毁,性能卡顿?"
"表单输入内容在路由切换后清空?"

keep-alive 就是解决这些问题的"银弹"。

它能缓存组件状态,避免重复渲染,大幅提升用户体验。

但你是否真正理解:

  • keep-alive 缓存的是什么?
  • 它是如何实现的?
  • LRU 策略如何工作?

本文将从 源码级 深度解析 keep-alive 的核心机制。


一、keep-alive 是什么?

✅ 核心定义

keep-alive 是 Vue 的内置抽象组件,用于:

缓存动态组件或路由视图的实例,避免重复销毁与重建

📌 基础用法

vue 复制代码
<!-- 缓存所有匹配的组件 -->
<keep-alive>
  <component :is="currentView"></component>
</keep-alive>

<!-- 或缓存路由视图 -->
<keep-alive>
  <router-view></router-view>
</keep-alive>

二、三大核心属性

属性 类型 说明
include `string RegExp
exclude `string RegExp
max `number string`

💡 实战示例

vue 复制代码
<keep-alive 
  include="UserInfo,OrderList"
  exclude="SearchResult"
  :max="10"
>
  <router-view />
</keep-alive>
  • ✅ 缓存 UserInfoOrderList
  • ❌ 不缓存 SearchResult
  • ⚠️ 最多缓存 10 个实例,超出时按 LRU 策略清除。

三、keep-alive 如何工作?源码级解析

🧩 核心数据结构

js 复制代码
// 初始化缓存
created() {
  this.cache = Object.create(null); // 存储 { key: vnode }
  this.keys = []; // 存储 key,实现 LRU
}
  • cache: 缓存对象,keyvnode 映射;
  • keys: key 数组,记录访问顺序,实现 LRU(最近最少使用) 策略。

🔁 render 函数核心流程

步骤 1:获取第一个子组件

js 复制代码
const slot = this.$slots.default;
const vnode = getFirstComponentChild(slot);

💡 keep-alive 只对第一个子组件生效。


步骤 2:判断是否需要缓存

js 复制代码
const name = getComponentName(vnode.componentOptions);

// 不在 include 或在 exclude 中,直接返回
if (
  (include && !matches(include, name)) ||
  (exclude && matches(exclude, name))
) {
  return vnode;
}

步骤 3:生成唯一 key

js 复制代码
const key = vnode.key == null
  ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
  : vnode.key;

❗ 为什么不用 cid 唯一标识?

因为同一个构造函数可以注册为多个本地组件cid 相同但组件不同。


步骤 4:缓存命中与 LRU 更新

js 复制代码
if (cache[key]) {
  // 命中缓存
  vnode.componentInstance = cache[key].componentInstance;
  
  // LRU:将 key 移到末尾(最新使用)
  remove(keys, key);
  keys.push(key);
} else {
  // 未命中,加入缓存
  cache[key] = vnode;
  keys.push(key);
  
  // 超出 max,清除最久未使用的
  if (max && keys.length > parseInt(max)) {
    pruneCacheEntry(cache, keys[0], keys);
  }
}

步骤 5:标记 keepAlive

js 复制代码
vnode.data.keepAlive = true;

这个标记告诉 Vue:跳过组件的 createdmounted 等生命周期


四、缓存的是什么?真相揭秘

✅ 正确答案

keep-alive 缓存的是:

组件的 vnode 实例 和 componentInstance(组件实例)

包括:

  • 组件的 DOM 结构;
  • 组件的 data 状态;
  • 事件监听器;
  • 子组件树。

❌ 常见误解

  • ❌ 不是只缓存 HTML 字符串;
  • ❌ 不是只缓存 data 对象;
  • ❌ 不是"冻结"组件。

五、生命周期钩子:activateddeactivated

当组件被 keep-alive 包裹时,会新增两个生命周期钩子:

钩子 触发时机
activated 组件被激活(从缓存中显示)时调用
deactivated 组件被缓存(切换离开)时调用

💡 实战应用

js 复制代码
export default {
  data() {
    return {
      timer: null
    };
  },
  activated() {
    // 页面可见时恢复轮询
    this.timer = setInterval(() => {
      console.log('轮询中...');
    }, 1000);
  },
  deactivated() {
    // 页面隐藏时清除轮询,避免内存泄漏
    clearInterval(this.timer);
  }
}

六、首次渲染 vs 缓存渲染

📌 首次渲染

js 复制代码
init(vnode) {
  // 初次渲染,componentInstance 为 undefined
  const child = createComponentInstanceForVnode(vnode);
  child.$mount();
}
  • 正常执行 beforeCreatecreatedmounted

📌 缓存渲染

js 复制代码
init(vnode) {
  if (vnode.componentInstance && vnode.data.keepAlive) {
    // 直接复用实例,执行 prepatch
    componentVNodeHooks.prepatch(vnode, vnode);
  }
}
  • 跳过 createdmounted
  • 直接将缓存的 DOM 插入文档;
  • 触发 activated 钩子。

七、LRU 缓存策略详解

📌 LRU(Least Recently Used)

"最近使用的,将来最可能再次使用。"

🔁 工作机制

操作 行为
新组件 插入 keys 数组末尾
命中缓存 将 key 从原位置移除,插入末尾
超出 max 移除 keys[0](最久未使用)

📊 示例:max=3

text 复制代码
操作: A → B → C → A → D
keys: [A] → [A,B] → [A,B,C] → [B,C,A] → [C,A,D]
  • D 加入时,B 被清除(最久未使用)。

八、最佳实践与避坑指南

✅ 最佳实践

  1. 合理设置 max:避免内存泄漏;
  2. deactivated 中清理资源:定时器、事件监听;
  3. 使用 include 精准控制:避免缓存重型组件。

❌ 常见错误

  • 忘记给组件命名(name 选项);
  • created 中启动轮询,未在 deactivated 中清除;
  • 缓存大量列表组件,导致内存飙升。

💡 结语

"keep-alive 是性能与体验的平衡艺术。"

要点 说明
缓存内容 vnode + 组件实例
核心机制 cache + keys + LRU
生命周期 activated / deactivated
适用场景 频繁切换的路由、表单、列表

掌握 keep-alive,你就能:

✅ 显著提升应用流畅度;

✅ 保留用户交互状态;

✅ 优化关键路径性能。

相关推荐
Qrun7 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp7 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.8 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl10 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫11 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友11 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理13 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻13 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
mapbar_front14 小时前
在职场生存中如何做个不好惹的人
前端
牧杉-惊蛰14 小时前
纯flex布局来写瀑布流
前端·javascript·css