跳到主要内容

keep-alive 组件原理

<keep-alive> 是 Vue 面试里的高频题,因为它同时涉及:

  • 组件缓存
  • 生命周期
  • 路由切换
  • 性能优化

如果只回答“它可以缓存组件,避免重复渲染”,只能算入门。更完整的回答应该是:

keep-alive 不是真的把组件“留在 DOM 上不动”,而是 缓存组件实例和子树,在切换时走激活/失活,而不是重新挂载/卸载

面试速答(30 秒版 TL;DR)

  • keep-alive 是一个抽象组件,本身不渲染真实 DOM。
  • 它会缓存被包裹组件的 VNode / 组件实例 / 子树状态
  • 组件切走时不是卸载,而是 deactivated;切回来时不是重新创建,而是 activated
  • 可通过 includeexcludemax 控制缓存范围和数量。
  • max 本质上是一个近似 LRU 的缓存淘汰策略。

1. keep-alive 是什么?

它是一个内置抽象组件,常见写法:

<keep-alive>
<router-view />
</keep-alive>

“抽象组件”意味着:

  • 它主要参与组件调度,不生成自己的真实 DOM
  • 它关注的是“如何缓存和复用子组件”

2. 它缓存的到底是什么?

不是单纯缓存一份 HTML,而是缓存:

  • 组件实例
  • 渲染出的子树 VNode
  • 组件内部响应式状态
  • DOM 关联关系

所以切回来时,用户之前输入过的表单、滚动位置、局部状态,常常还能保住。

3. 核心机制:挂载一次,后续激活/失活

正常组件切换:

  • 离开时卸载
  • 回来时重新创建实例并重新挂载

keep-alive 包裹后:

  • 首次进入:正常挂载
  • 离开:不卸载,而是失活
  • 再回来:不重新创建,而是激活

所以对应的生命周期会变成:

  • 首次进入:mounted + activated
  • 切走:deactivated
  • 切回:activated
  • 真正被移出缓存:才走 beforeUnmount / unmounted

4. 它是怎么找到“同一个组件”的?

缓存命中的关键,一般基于:

  • 组件类型
  • key

如果是同一路由组件,但你给了不同的 key,那 Vue 会认为它们是不同缓存项。

例如:

<router-view :key="$route.fullPath" />

这会导致参数变化时更容易重新创建,而不是复用原缓存。

5. include / exclude / max 的作用

5.1 include

只缓存命中的组件名。

<keep-alive :include="['UserList', 'OrderList']">
<router-view />
</keep-alive>

5.2 exclude

排除不需要缓存的组件名。

5.3 max

限制缓存数量。超过上限后,会淘汰最久未使用的缓存项。

这个思路本质上接近 LRU

  • 最近访问过的,保留
  • 很久没访问的,优先淘汰

6. 一个简化版心智模型

可以把 keep-alive 想成一个 Map

const cache = new Map()

function render(vnode) {
const key = vnode.key ?? vnode.type

if (cache.has(key)) {
vnode.component = cache.get(key).component
activate(vnode)
} else {
mount(vnode)
cache.set(key, vnode)
}
}

真实实现比这复杂得多,但核心思想就是:

  • 命中缓存就复用
  • 没命中就首次挂载并记录

7. 为什么 keep-alive 常和 router-view 一起用?

因为列表页、表单页、详情页切换时,经常希望保留:

  • 列表筛选条件
  • 滚动位置
  • 已输入但未提交的表单内容

如果每次都卸载重建,用户体验会很差。

这是它最典型的落地场景。

8. 常见问题

8.1 为什么切换回来不触发 mounted

因为组件根本没被重新挂载,只是从“失活”切回“激活”。

这时你应该把“每次回来都要执行”的逻辑写在:

  • Vue 2:activated
  • Vue 3:onActivated

8.2 为什么有时看起来还是重新渲染了?

常见原因有:

  • include / exclude 不匹配
  • 组件名不符合预期
  • key 变了
  • max 触发了淘汰

8.3 keep-alive 能解决所有性能问题吗?

不能。它适合“重复切换、状态值得保留”的组件。若缓存的是:

  • 超大列表
  • 大量复杂图表
  • 占内存很重的页面

反而可能把内存顶高。

9. 面试高频答法

Q1:keep-alive 为什么不触发销毁钩子?

:因为它不是让组件真正卸载,而是把组件从活跃树中移开并缓存起来。组件进入的是失活状态,对应 deactivated,而不是销毁状态。

Q2:keep-alivemax 是怎么工作的?

:本质上是限制缓存条目数,超过后按近似 LRU 的策略淘汰最久未访问的缓存项。被淘汰的组件才会真正卸载。

Q3:什么时候不建议用 keep-alive

:页面状态不值得保留、组件内存占用很大、切回来必须强制重新拉全量数据时,不一定适合用。

速记要点

  • keep-alive 缓存的是实例和子树,不是静态 HTML
  • 首次挂载,后续是 activated / deactivated
  • 命中缓存看组件类型和 key
  • include / exclude / max 控制缓存范围和淘汰