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

相关推荐
Deca~1 小时前
VueVirtualLazyTree-支持懒加载的虚拟树
前端·javascript·vue.js
2501_944526421 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 主题切换实现
android·开发语言·javascript·python·flutter·游戏·django
爱上妖精的尾巴2 小时前
7-11 WPS JS宏 对象的属性值为函数的写法与用法
前端·javascript·wps·js宏·jsa
爱上妖精的尾巴2 小时前
7-12 WPS JS宏 this、return用构造函数自定义类-1:对象内部函数,外部调用的写法
前端·javascript·wps·js宏·jsa
har01d2 小时前
AI生成的 vue3 日历组件,显示农历与节日,日期可选择,年月可切换
前端·vue.js·节日
HIT_Weston2 小时前
106、【Ubuntu】【Hugo】搭建私人博客:模糊搜索 Fuse.js(二)
linux·javascript·ubuntu
冲刺逆向2 小时前
【js逆向案例六】创宇盾(加速乐)通杀模版
java·前端·javascript
我穿棉裤了2 小时前
文字换行自动添加换行符“-”
前端·javascript·vue.js