Skip to content

<KeepAlive> 是 Vue 3 中一个极其重要的内置组件,它通过缓存组件实例来避免重复的销毁与重建,从而保留组件状态、提升性能。理解其缓存机制、配置选项和生命周期钩子,是构建高性能 Vue 应用的关键。


一、核心作用:保存组件状态

当一个组件被 <KeepAlive> 包裹时:

  • 首次渲染:正常创建、挂载。
  • 切换离开不销毁,而是被缓存起来(失活)。
  • 再次进入不重新创建,而是从缓存中取出并激活

保留了什么?

  • 组件实例datarefreactive 状态。
  • DOM 状态:表单输入、滚动位置、动画状态。
  • 子组件树:整个组件子树都被缓存。

对比普通组件

操作普通组件<KeepAlive> 组件
首次进入createdmountedcreatedmounted
切换离开beforeUnmountunmountedonDeactivated
再次进入createdmountedonActivated

二、实现原理:LRU 缓存机制

<KeepAlive> 的内部实现类似于一个 LRU (Least Recently Used) 缓存

1. 核心数据结构

  • cacheMap 类型,存储组件实例的 VNode
    • Key:组件的唯一标识(通常由组件的 keyname 生成)。
    • Value:被缓存的组件的 VNode
  • keysSet 类型,记录缓存的访问顺序(用于 LRU)。

2. 缓存流程

js
// 伪代码:渲染过程
function processKeepAlive(n1, n2, container, anchor) {
  const instance = n2.component;
  const key = getKey(n2); // 获取组件 key

  if (n1) {
    // 更新
    const cached = cache.get(key);
    if (cached && shouldInclude(cached)) {
      // 命中缓存
      instance.subTree = cached.subTree; // 复用缓存的虚拟 DOM
      move(cached, container, anchor);   // 移动到正确位置(Teleport 风格)
      activate(instance);                // 触发生命周期
    } else {
      // 未命中,重新渲染
      renderComponent(n2);
      cache.set(key, n2);
      keys.add(key);
    }
  } else {
    // 首次挂载
    renderComponent(n2);
    cache.set(key, n2);
    keys.add(key);
  }
}

3. move() 操作

  • 类似 Teleport<KeepAlive> 使用 move() 将缓存的 VNode 对应的 DOM 节点移动到正确位置。
  • 这确保了 DOM 节点的复用,而不是重新创建。

三、配置选项的实现

<KeepAlive> 支持三个关键属性:includeexcludemax

1. includeexclude

  • 作用:根据组件的 name 决定是否缓存。
  • 类型:字符串、正则、数组。
vue
<KeepAlive 
  :include="/^My.*/" 
  :exclude="['Modal']"
>
  <component :is="currentView" />
</KeepAlive>
实现原理
js
function shouldInclude(componentName, pattern) {
  if (typeof pattern === 'string') {
    return pattern.split(',').includes(componentName);
  } else if (pattern instanceof RegExp) {
    return pattern.test(componentName);
  } else if (Array.isArray(pattern)) {
    return pattern.includes(componentName);
  }
  return false;
}

// 在缓存/取缓存时检查
if (include && !shouldInclude(name, include)) return false;
if (exclude && shouldInclude(name, exclude)) return false;
  • 优先级exclude 优先于 include

2. max

  • 作用:限制缓存的最大数量,防止内存泄漏。
  • 实现:LRU 策略。
js
// 当缓存数量超过 max 时
if (max && cache.size > max) {
  // 找到最久未使用的 key
  const oldestKey = keys.values().next().value;
  const oldestVNode = cache.get(oldestKey);
  
  // 销毁最久未使用的组件
  unmount(oldestVNode);
  
  // 从缓存中移除
  cache.delete(oldestKey);
  keys.delete(oldestKey);
}
  • LRU 更新:每次组件被激活(onActivated),其 key 会从 keys 中移除并重新添加到末尾,确保“最近使用”的排在后面。

四、生命周期钩子:onActivatedonDeactivated

由于 <KeepAlive> 组件不会被销毁,mounted/unmounted 钩子只在首次和最终销毁时触发。为此,Vue 提供了两个专用钩子。

1. onActivated()

  • 触发时机
    • 组件从缓存中被取出并重新插入 DOM 时。
    • 首次 mounted 之后也会触发一次。
  • 用途
    • 启动定时器、动画。
    • 恢复事件监听。
    • 触发数据更新(如轮询)。
js
import { onActivated, onDeactivated } from 'vue';

onActivated(() => {
  console.log('组件被激活');
  startTimer();
});

2. onDeactivated()

  • 触发时机
    • 组件被切换离开,放入缓存时。
  • 用途
    • 清理定时器、取消订阅,避免内存泄漏。
    • 暂停动画、音乐。
js
onDeactivated(() => {
  console.log('组件被缓存(失活)');
  clearInterval(timer);
  // 不要调用 unmount,KeepAlive 会处理
});

钩子执行顺序

js
// 首次进入
created → mounted → onActivated

// 切换离开
onDeactivated

// 再次进入
onActivated

// 最终销毁(如被 exclude 或 max 淘汰)
onDeactivated → beforeUnmount → unmounted

五、与 v-ifv-show 的区别

特性v-ifv-show<KeepAlive>
DOM 存在销毁/重建始终存在缓存,DOM 可能被移除
状态保留
初始渲染开销高(每次重建)高(首次),低(后续)
适用场景条件渲染,不常切换频繁切换,简单元素保留状态的复杂组件切换
  • v-show:通过 display: none 切换,DOM 始终在内存中,但组件实例不缓存onDeactivated 不会触发。
  • <KeepAlive>:更智能,可以控制缓存策略。

六、最佳实践

  1. 合理使用 max

    • 避免缓存过多组件导致内存压力。
    • 结合业务场景设置合理的上限。
  2. onDeactivated 中清理资源

    • 定时器、WebSocket、事件监听器必须清理。
    • 否则会导致内存泄漏和后台运行。
  3. 配合 key 使用

    • 确保组件有稳定的 key,以便正确匹配缓存。
  4. 避免缓存重型组件

    • 如包含大量数据或复杂计算的组件,可能不值得缓存。
  5. 与路由结合

js
// router.js
{
  path: '/home',
  component: () => import('./views/Home.vue'),
  meta: { keepAlive: true }
}
vue
<KeepAlive>
  <router-view v-if="$route.meta.keepAlive" />
</KeepAlive>
<router-view v-if="!$route.meta.keepAlive" />

七、总结

<KeepAlive> 的核心机制是:

  • 缓存实例:通过 Map 存储组件的 VNode,保留状态。
  • LRU 策略max 限制缓存数量,keys 维护访问顺序。
  • 条件过滤include/exclude 基于组件 name 决定是否缓存。
  • 专用钩子onActivated/onDeactivated 替代 mounted/unmounted 的部分职责。
  • 高效移动:使用 move() 操作复用 DOM 节点。

它是 Vue 实现高性能动态组件切换的基石。正确使用 <KeepAlive>,可以显著提升用户体验,避免不必要的重复渲染。