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

相关推荐
发现一只大呆瓜28 分钟前
虚拟列表:支持“向上加载”的历史消息(Vue 3 & React 双版本)
前端·javascript·面试
阔皮大师1 小时前
INote轻量文本编辑器
java·javascript·python·c#
lbb 小魔仙1 小时前
【HarmonyOS实战】React Native 表单实战:自定义 useReactHookForm 高性能验证
javascript·react native·react.js
_codemonster1 小时前
Vue的三种使用方式对比
前端·javascript·vue.js
全栈前端老曹2 小时前
【MongoDB】Node.js 集成 —— Mongoose ORM、Schema 设计、Model 操作
前端·javascript·数据库·mongodb·node.js·nosql·全栈
低代码布道师2 小时前
Next.js 16 全栈实战(一):从零打造“教培管家”系统——环境与脚手架搭建
开发语言·javascript·ecmascript
一位搞嵌入式的 genius3 小时前
深入 JavaScript 函数式编程:从基础到实战(含面试题解析)
前端·javascript·函数式
choke2333 小时前
[特殊字符] Python 文件与路径操作
java·前端·javascript
wqq63108553 小时前
Python基于Vue的实验室管理系统 django flask pycharm
vue.js·python·django
Deng9452013143 小时前
Vue + Flask 前后端分离项目实战:从零搭建一个完整博客系统
前端·vue.js·flask