Vue3底层原理——keep-alive

一、keep-alive 概述

keep-alive​ 不是缓存 DOM,而是缓存「组件 VNode + 组件实例(但 VNode 里持有组件实例)」,它通过"劫持组件卸载流程",把 destroy 变成 deactivate

源码位置:

Plain 复制代码
packages/runtime-core/src/components/KeepAlive.ts

keep-alive 是一个抽象组件,它不产生真实 DOM,只影响子组件的渲染/生命周期(只"包裹 & 接管"子组件)。

为什么 keep-alive 不缓存 DOM 快照?

因为:

  • DOM 是副产品,真正的"状态"在组件实例中
  • DOM 可以随时重建

所以:Vue 缓存的是"状态",不是"视图" 。

二、keep-alive 渲染流程

2.1 核心数据结构

KeepAlive.ts 中,有一个 setup() 函数,里面有两个核心数据结构:

TypeScript 复制代码
const cache = new Map<CacheKey, VNode>() // key -> VNode
const keys = new Set<CacheKey>() // 维护 LRU 顺序

2.2 render 阶段拦截子节点

TypeScript 复制代码
const vnode = slots.default()[0]

keep-alive 只关心第一个组件子节点

2.3 设置缓存

TypeScript 复制代码
const key = vnode.key ?? vnode.type // 决定缓存是否命中
2.3.1 命中缓存
TypeScript 复制代码
const cachedVNode = cache.get(key)

vnode.component = cachedVNode.component
vnode.el = cachedVNode.el

// 并打上标记
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
2.3.2 首次渲染
TypeScript 复制代码
cache.set(key, vnode)
keys.add(key)

// 打上标记
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE

三、keep-alive "阻止组件被卸载"

3.1 Vue 正常卸载组件会做什么?

Plain 复制代码
unmount(vnode)
 ↓
unmountComponent(instance)
 ↓
stop effects
 ↓
卸载 DOM

3.2 keep-alive 如何拦截?

在卸载阶段,Vue 会检查 flag:

TypeScript 复制代码
if (shapeFlag & COMPONENT_SHOULD_KEEP_ALIVE) {
  deactivate(vnode) // destroy 被替换成 deactivate
  return
}

3.3 deactivate 实际做了什么?

TypeScript 复制代码
function deactivate(vnode) {
  move(vnode, storageContainer) // 移动 DOM 到隐藏容器(effect / state / refs 全保留)
  queuePostRenderEffect(() => {
    invokeArrayFns(instance.da) // deactivated hooks
  })
}

四、activated / deactivated 生命周期

在组件实例上:

TypeScript 复制代码
instance.a  // activated hooks
instance.da // deactivated hooks

注册来源:

JavaScript 复制代码
onActivated(fn)
onDeactivated(fn)

触发时机:

activated / deactivated 正是与 Vue 调度系统有关:

TypeScript 复制代码
queuePostRenderEffect(hook) // DOM 更新后,批量调度

所以缓存的视图尽管不在界面上,却仍然会触发响应式调度。

五、include / exclude / max

5.1 include / exclude(基于组件名)

TypeScript 复制代码
function matches(pattern, name) {
  return pattern.split(',').includes(name)
}

在 render 阶段:

TypeScript 复制代码
if (!matches(include, name)) {
  return vnode // 不缓存
}

5.2 max(LRU 缓存)

没错,这个 LRU 缓存正是咱们刷力扣经典题"LRU 缓存"算法!

TypeScript 复制代码
if (keys.size > max) {
  pruneCacheEntry(keys.values().next().value) // 看到这个写法,用 js/ts 刷算法的同学是不是突然恍然大悟!
}

prune 可以从 cache 中真正 unmount 组件。

这是 keep-alive 唯一会"真的销毁组件"的地方。

相关推荐
尘中客1 小时前
放弃 Echarts?前端直接渲染后端高精度 SVG 矢量图流的踩坑记录
前端·javascript·echarts·前端开发·svg矢量图·echarts避坑
小彭努力中1 小时前
199.Vue3 + OpenLayers 实现:点击 / 拖动地图播放音频
前端·vue.js·音视频·openlayers·animate
2501_916007471 小时前
网站爬虫原理,基于浏览器点击行为还原可接口请求
前端·javascript·爬虫·ios·小程序·uni-app·iphone
Highcharts.js3 小时前
适合报表系统的可视化图表|Highcharts支持直接导出PNG和PDF
javascript·数据库·react.js·pdf
慧一居士3 小时前
Vue项目中,何时使用布局、子组件嵌套、插槽 对应的使用场景,和完整的使用示例
前端·vue.js
叫我一声阿雷吧3 小时前
JS 入门通关手册(35):执行上下文、调用栈与作用域链深度解析
javascript·作用域链·js进阶·执行上下文·调用栈·变量提升·闭包原理
Jave21084 小时前
实现全局自定义loading指令
前端·vue.js
Amumu121385 小时前
Js:正则表达式(一)
开发语言·javascript·正则表达式
月光宝盒造梦师7 小时前
Ant Design Ellipsis 中的判断逻辑 isEleEllipsis 方法非常消耗性能
javascript·react·优化
酉鬼女又兒8 小时前
零基础快速入门前端ES6 核心特性详解:Set 数据结构与对象增强写法(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·职场和发展·蓝桥杯·es6