Vue 中 keep-alive 组件的原理与实践详解

Vue 中 keep-alive 组件的原理与实践详解

在 Vue 开发中,我们经常会遇到这样的场景:列表页筛选数据后进入详情页,返回列表页时筛选状态却丢失了------这是因为默认情况下,Vue 会销毁不活动的组件,重新进入时会创建新实例。而 keep-alive 组件正是为解决这类问题而生,它能缓存不活动的组件实例,避免重复渲染,提升用户体验。本文将从核心概念、使用方式、源码解析到渲染机制,全面拆解 keep-alive 的工作原理。

一、keep-alive 核心概念

keep-alive 是 Vue 内置的全局抽象组件,具有以下关键特性:

  • 自身不会渲染为真实 DOM,也不会出现在父组件链中
  • 包裹动态组件或路由组件时,会缓存不活动的组件实例(而非销毁)
  • 支持通过配置参数控制缓存范围和数量上限
  • 本质是通过缓存组件的虚拟 DOM(VNode),实现组件状态的持久化

二、keep-alive 基本使用

keep-alive 接收三个核心 props 参数,支持灵活控制缓存行为:

参数 类型 说明
include 字符串/正则/数组 名称匹配的组件会被缓存
exclude 字符串/正则/数组 名称匹配的组件不会被缓存
max 字符串/数字 缓存组件的最大数量,超出按 LRU 算法置换

2.1 包裹动态组件

vue 复制代码
<keep-alive :include="allowList" :exclude="noAllowList" :max="5">
  <component :is="currentComponent"></component>
</keep-alive>

2.2 包裹路由组件

vue 复制代码
<keep-alive :include="['List', 'Detail']" :max="3">
  <router-view></router-view>
</keep-alive>

注意:includeexclude 匹配的是组件的 name 选项,而非组件文件名或标签名。

三、keep-alive 源码深度解析

keep-alive 的核心逻辑集中在 src/core/components/keep-alive.js 中,我们从组件结构、生命周期、核心函数三方面展开分析。

3.1 组件基础结构

javascript 复制代码
export default {
  name: 'keep-alive',
  abstract: true, // 关键:标记为抽象组件,不参与DOM渲染
  props: {
    include: patternTypes, // 允许缓存的组件匹配规则
    exclude: patternTypes, // 禁止缓存的组件匹配规则
    max: [String, Number] // 缓存数量上限
  },
  // 生命周期与核心方法...
}

abstract: truekeep-alive 不渲染自身的关键:Vue 在初始化生命周期时,会跳过抽象组件,不将其加入父组件链(见 src/core/instance/lifecycle.js)。

3.2 生命周期钩子

3.2.1 created:初始化缓存容器
javascript 复制代码
created() {
  this.cache = Object.create(null) // 存储缓存的VNode(虚拟DOM)
  this.keys = [] // 存储缓存组件的key,用于LRU排序
}
  • cache:对象结构,key 为组件唯一标识,value 为组件的 VNode 实例
  • keys:数组结构,维护缓存组件的访问顺序,支持 LRU 算法
3.2.2 mounted:监听缓存规则变化
javascript 复制代码
mounted() {
  // 监听include变化,更新缓存(保留匹配的组件)
  this.$watch('include', val => {
    pruneCache(this, name => matches(val, name))
  })
  // 监听exclude变化,更新缓存(移除匹配的组件)
  this.$watch('exclude', val => {
    pruneCache(this, name => !matches(val, name))
  })
}

通过监听 includeexclude 的变化,动态清理不符合规则的缓存组件。

3.2.3 destroyed:清理所有缓存
javascript 复制代码
destroyed() {
  // 遍历缓存,销毁所有组件实例并清空缓存
  for (const key in this.cache) {
    pruneCacheEntry(this.cache, key, this.keys)
  }
}

3.3 核心工具函数

3.3.1 pruneCacheEntry:销毁单个缓存项
javascript 复制代码
function pruneCacheEntry(cache, key, keys, current) {
  const cached = cache[key]
  // 如果缓存存在且不是当前活跃组件,执行组件销毁
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null // 清空缓存引用
  remove(keys, key) // 从key列表中移除
}

作用:销毁组件实例、释放内存、更新缓存列表,是缓存清理的核心方法。

3.3.2 render:缓存逻辑核心

keep-alive 的核心逻辑都在 render 函数中,负责组件的缓存判断、存储和更新,步骤如下:

  1. 获取目标组件:获取包裹的第一个子组件的 VNode 和组件配置
javascript 复制代码
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot) // 仅处理第一个子组件
const componentOptions = vnode && vnode.componentOptions
  1. 缓存规则校验 :判断组件是否符合 include/exclude 规则
javascript 复制代码
const name = getComponentName(componentOptions) // 获取组件name
const { include, exclude } = this
// 不满足include 或 命中exclude,直接返回VNode,不缓存
if ((include && !matches(include, name)) || (exclude && matches(exclude, name))) {
  return vnode
}
  1. 生成缓存key:基于组件唯一标识生成key(避免重复缓存)
javascript 复制代码
const key = vnode.key || `${componentOptions.Ctor.cid}::${componentOptions.tag}`
  • 优先使用组件自身的 key
  • key 时,用「组件构造函数ID + 标签名」拼接
  1. 缓存命中处理:若组件已缓存,更新访问顺序(LRU核心)
javascript 复制代码
if (cache[key]) {
  // 复用缓存的组件实例
  vnode.componentInstance = cache[key].componentInstance
  // 移除原有key,添加到数组末尾(标记为最近访问)
  remove(keys, key)
  keys.push(key)
}
  1. 缓存未命中处理:新增缓存,检查数量上限
javascript 复制代码
else {
  cache[key] = vnode // 缓存VNode
  keys.push(key)
  // 超过max限制,删除最久未访问的缓存(LRU置换)
  if (this.max && keys.length > parseInt(this.max)) {
    pruneCacheEntry(cache, keys[0], keys, this._vnode)
  }
}
  1. 标记缓存状态 :设置 keepAlive 标识,供后续渲染使用
javascript 复制代码
vnode.data.keepAlive = true

四、keep-alive 渲染机制

Vue 组件的常规渲染流程为:render -> VNode -> patch -> 真实DOM,而 keep-alive 包裹的组件会在这个流程中加入缓存判断,核心在于 patch 阶段的 createComponent 函数。

4.1 常规组件渲染流程

复制代码
new Vue → init → $mount → compile → render → VNode → patch → 真实DOM
  • 初次渲染:patch 直接将 VNode 转为真实 DOM
  • 后续渲染:patch 通过 diff 算法对比新旧 VNode,打补丁更新 DOM

4.2 keep-alive 渲染特殊处理

keep-alive 包裹的组件渲染逻辑在 src/core/vdom/patch.jscreateComponent 函数中:

javascript 复制代码
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  const i = vnode.data
  if (isDef(i)) {
    // 判断是否为缓存组件(vnode.data.keepAlive为true)
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false) // 初次渲染时初始化组件
    }
    // 若为缓存组件,直接复用DOM
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      // 直接插入缓存的真实DOM(vnode.elm),无需重新渲染
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
关键逻辑区分:
  • 初次访问vnode.componentInstanceundefined,执行 init 钩子初始化组件,生成 DOM 并缓存
  • 再次访问vnode.componentInstance 已存在(缓存的实例),直接调用 insert 插入缓存的 DOM,跳过组件初始化和渲染过程

五、核心总结

keep-alive 的本质是基于 VNode 的缓存管理组件,核心设计思路可概括为:

  1. 抽象组件特性 :通过 abstract: true 实现自身不渲染,仅作为缓存容器
  2. 缓存存储结构 :用 cache 对象存储 VNode,keys 数组维护访问顺序
  3. LRU 置换算法 :超过 max 时删除 keys 数组头部(最久未访问)的缓存项
  4. 渲染优化逻辑:复用缓存的 VNode 和组件实例,跳过初始化和 diff 过程,直接插入真实 DOM
  5. 动态缓存控制 :通过监听 include/exclude 变化,实时清理无效缓存

合理使用 keep-alive 能显著提升页面切换性能(尤其是列表页、表单页等需要保留状态的场景),但需注意避免无限制缓存过多组件,以免占用过多内存。

🎯 2025年最新大厂前端场景面试题(完整题库+解析)

一份系统整理的前端专项面试资料,内容涵盖 JavaScript、React、Vue、Node.js、性能优化、工程化、浏览器原理、网络协议 等高频考点,所有题目均来自大厂真实场景与近期面试趋势。

📚 资料详情

方向:前端开发

格式:PDF

页数:251页(含详细答案与扩展解析)

适用:面试冲刺、体系复盘、技术查漏

📥 获取方式

👉 夸克网盘链接:https://pan.quark.cn/s/bcf695cf5ede

(可直接下载,无需提取码)

希望这份详细的题库能助你高效备战,顺利拿到心仪的 Offer!加油

相关推荐
FeelTouch Labs2 小时前
Nginx核心架构设计
运维·前端·nginx
雪球工程师团队3 小时前
别再“苦力”写后台,Spec Coding “跑” 起来
前端·ai编程
m0_471199633 小时前
【场景】前端怎么解决离线收银、数据同步异常等场景问题
前端·javascript
Curvatureflight3 小时前
前端性能优化实战:从3秒到300ms的加载速度提升
前端·人工智能·性能优化
用户99045017780093 小时前
ruoyi集成dmn规则引擎
前端
袋鱼不重3 小时前
AI入门知识点:什么是 AIGC、多模态、RAG、Function Call、Agent、MCP?
前端·aigc·ai编程
小胖霞4 小时前
企业级全栈项目(14) winston记录所有日志
vue.js·前端框架·node.js
NuLL4 小时前
空值检测工具函数-统一规范且允许自定义配置的空值检测方案
前端
栀秋6664 小时前
“无重复字符的最长子串”:从O(n²)哈希优化到滑动窗口封神,再到DP降维打击!
前端·javascript·算法