面试题:什么是 Vue 的 keep-alive?它是如何实现的?具体缓存了什么内容?

什么是 Vue 的 keep-alive

<keep-alive> 是 Vue 提供的一个内置组件,用于缓存不活动的组件实例,而不是销毁它们。当组件被 <keep-alive> 包裹时,它的状态(如数据、DOM 等)会被保留,避免重复渲染和销毁,从而提升性能。


1. keep-alive 的作用

  • 缓存组件实例:当组件切换时,被缓存的组件不会被销毁,而是保留在内存中。
  • 保留组件状态:组件的状态(如数据、DOM、事件监听器等)会被保留,避免重新渲染。
  • 提升性能:减少组件的初始化和销毁开销,特别适合频繁切换的组件(如 Tab 切换、路由切换)。

2. keep-alive 的使用

基本用法

<template>
  <keep-alive>
    <component :is="currentComponent"></component>
  </keep-alive>
</template>

<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

export default {
  data() {
    return {
      currentComponent: 'ComponentA'
    };
  },
  components: {
    ComponentA,
    ComponentB
  }
};
</script>

结合 Vue Router 使用

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

3. keep-alive 的实现原理

<keep-alive> 的实现原理主要包括以下几个部分:

(1)缓存组件实例

  • keep-alive 内部维护了一个缓存对象(cache),用于存储被缓存的组件实例。
  • 当组件被切换时,keep-alive 会将组件实例从 DOM 中移除,但保留在 cache 中。

(2)LRU 缓存策略

  • keep-alive 使用 LRU(Least Recently Used,最近最少使用)算法管理缓存。
  • 当缓存数量超过 max 设置的最大值时,最久未使用的组件实例会被销毁。

(3)生命周期钩子

  • 当组件被缓存时,会触发 deactivated 钩子。
  • 当组件被激活时,会触发 activated 钩子。

4. keep-alive 缓存了什么内容?

<keep-alive> 缓存了以下内容:

  1. 组件实例:组件的 Vue 实例(包括数据、方法、计算属性等)。
  2. DOM 状态:组件的 DOM 结构和状态(如表单输入、滚动位置等)。
  3. 事件监听器:组件绑定的事件监听器。
  4. 子组件:组件的子组件也会被缓存。

5. keep-alive 的属性和方法

属性

  • include:只有匹配的组件会被缓存(支持字符串、正则表达式或数组)。
  • exclude:匹配的组件不会被缓存(支持字符串、正则表达式或数组)。
  • max:最多缓存多少个组件实例。

示例

<keep-alive :include="['ComponentA', 'ComponentB']" :max="10">
  <component :is="currentComponent"></component>
</keep-alive>

方法

  • $refs.keepAlive.cache:访问缓存的组件实例。
  • $refs.keepAlive.keys:访问缓存的组件键名。

6. keep-alive 的生命周期钩子

当组件被 <keep-alive> 缓存时,会触发以下生命周期钩子:

  • activated:组件被激活时调用(从缓存中恢复)。
  • deactivated:组件被停用时调用(进入缓存)。

示例

export default {
  activated() {
    console.log('Component activated');
  },
  deactivated() {
    console.log('Component deactivated');
  }
};

7. 源码解析(简化版)

以下是 keep-alive 的核心实现逻辑(简化版):

export default {
  name: 'keep-alive',
  abstract: true, // 抽象组件,不会出现在父子组件链中
  props: {
    include: [String, RegExp, Array],
    exclude: [String, RegExp, Array],
    max: [String, Number]
  },
  created() {
    this.cache = Object.create(null); // 缓存对象
    this.keys = []; // 缓存键名
  },
  destroyed() {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },
  mounted() {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name));
    });
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name));
    });
  },
  render() {
    const slot = this.$slots.default;
    const vnode = getFirstComponentChild(slot); // 获取第一个子组件
    const componentOptions = vnode && vnode.componentOptions;
    if (componentOptions) {
      const name = getComponentName(componentOptions);
      const { include, exclude } = this;
      if (
        (include && (!name || !matches(include, name))) ||
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }
      const { cache, keys } = this;
      const key = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key;
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        remove(keys, key);
        keys.push(key);
      } else {
        cache[key] = vnode;
        keys.push(key);
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  }
};

说明

  • cache:用于存储缓存的组件实例。
  • keys:用于存储缓存的键名,实现 LRU 算法。
  • pruneCacheEntry:销毁缓存中的组件实例。

总结

  • keep-alive 的作用:缓存不活动的组件实例,避免重复渲染和销毁。
  • 实现原理
    • 使用 cache 对象缓存组件实例。
    • 使用 LRU 算法管理缓存。
    • 触发 activateddeactivated 生命周期钩子。
  • 缓存内容:组件实例、DOM 状态、事件监听器、子组件等。
  • 适用场景:频繁切换的组件(如 Tab 切换、路由切换)。

通过 keep-alive,可以显著提升 Vue 应用的性能,特别是在需要保留组件状态的场景中。

THE END
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容