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 唯一会"真的销毁组件"的地方。

相关推荐
Highcharts.js44 分钟前
React 中原生支持使用 JSX 组件配置自定义表格 Highcharts Grid
前端·javascript·react.js·前端框架·jsx·highcharts·自定义表格
Java后端的Ai之路1 小时前
【AIAgent开发】-Agent工具封装成CLI(纯干货!!!)
java·javascript·人工智能·agent·cli
薛定谔的猫-菜鸟程序员1 小时前
Electron开发超详细教程(从入门到放弃)
前端·javascript·electron
小郎君6582 小时前
Vue 多标签页的加载态设计:为什么我没有用 Suspense 实现 PageLoading
vue.js·算法
htzyl2062 小时前
前端阶梯——第四十三章、实战二:待办清单To-Do
前端·javascript·html
yilaisai2 小时前
前端开发遇到的一些细节问题整理
vue.js
程序员大阳2 小时前
HTML+CSS+JS对话式实战(1)-开篇:认识我们要做的社团活动发布平台
javascript·css·html·实战·系统·平台·社团活动发布
速易达网络3 小时前
Three.js 动画入门教学:从场景搭建到流畅动效
开发语言·javascript·ecmascript
Hilaku3 小时前
CORS 到底在保护谁?90% 的前端理解错了跨域的本质
前端·javascript·程序员