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

相关推荐
满栀58512 小时前
vue动态路由效果
前端·javascript·vue.js·前端框架·vue
心运软件13 小时前
SpringBoot+ Vue校园社团管理平台的完整架构设计
vue.js·后端
jjw_zyfx13 小时前
css vue vite实现闪烁的呼吸效果
javascript·css·vue.js
sunly_13 小时前
React useActionState 的用法详解
前端·javascript·react.js
cyadyx14 小时前
【Three.js】Three.js 中加载 3D 模型
前端·javascript·3d
汉堡大王952714 小时前
前端工程师 TypeScript 上手指南:一条清晰的从入门到精通路线
前端·javascript·react.js
wear工程师14 小时前
防抖为什么会失效?React 面试里的闭包和定时器
javascript·react.js
浅水壁虎14 小时前
vue基础(第四章 Pinia)
前端·javascript·vue.js
张元清15 小时前
React useEventSource Hook:自带断线重连的 Server-Sent Events (2026)
javascript·react.js
学习星球15 小时前
Vue 3 实战:拆解 RealWorld 项目
前端·vue.js