一文详解Vue中keep-alive的实现原理

 更新时间:2025年10月30日 10:08:27   作者:阿珊和她的猫  
在 Vue.js 开发单页面应用(SPA)时,组件的频繁创建和销毁会带来一定的性能开销,为了优化这一问题,Vue 提供了 keep-alive 组件,本文将深入剖析 keep-alive 的实现原理,需要的朋友可以参考下

引言

在 Vue.js 开发单页面应用(SPA)时,组件的频繁创建和销毁会带来一定的性能开销。为了优化这一问题,Vue 提供了 keep-alive 组件。keep-alive 可以将包裹在其中的组件实例进行缓存,避免重复创建和销毁,从而提升应用的性能和用户体验。本文将深入剖析 keep-alive 的实现原理。

keep-alive 的基本使用

keep-alive 是一个内置组件,使用时只需将需要缓存的组件包裹在 <keep-alive> 标签内。示例如下:

<template>
  <div>
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>
    <button @click="toggleComponent">切换组件</button>
  </div>
</template>

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

export default {
  data() {
    return {
      currentComponent: 'ComponentA'
    };
  },
  components: {
    ComponentA,
    ComponentB
  },
  methods: {
    toggleComponent() {
      this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA';
    }
  }
};
</script>

在上述代码中,ComponentA 和 ComponentB 会被 keep-alive 缓存,切换时不会重新创建。

keep-alive 的实现原理

1. 缓存机制

keep-alive 内部使用一个对象 cache 来存储缓存的组件实例,键为组件的唯一标识,值为组件实例。同时,使用一个数组 keys 来存储这些组件的键,用于管理缓存的顺序。

2. 组件渲染过程

当 keep-alive 包裹的组件首次渲染时,keep-alive 会正常创建组件实例,并将其缓存到 cache 对象中,同时将对应的键添加到 keys 数组。当再次渲染该组件时,keep-alive 会从 cache 中取出缓存的组件实例进行渲染,而不是重新创建。

3. 生命周期钩子

keep-alive 会影响组件的生命周期钩子。被缓存的组件在首次进入时会触发 activated 钩子,在离开时会触发 deactivated 钩子,而不是 mounted 和 destroyed。这是因为组件实例并没有被真正销毁,只是被隐藏了起来。

4. 源码分析

以下是简化后的 keep-alive 源码分析:

export default {
  name: 'keep-alive',
  abstract: true, // 抽象组件,不会渲染到 DOM 中
  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) {
      // 销毁缓存的组件实例
      this.pruneCacheEntry(this.cache[key]);
    }
  },
  mounted() {
    // 监听 include 和 exclude 的变化
    this.$watch('include', val => {
      this.pruneCache(name => matches(val, name));
    });
    this.$watch('exclude', val => {
      this.pruneCache(name =>!matches(val, name));
    });
  },
  render() {
    const vnode = getFirstComponentChild(this.$slots.default); // 获取第一个子组件的虚拟节点
    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)) {
          this.pruneCacheEntry(keys[0]);
        }
      }

      vnode.data.keepAlive = true; // 标记组件被缓存
    }
    return vnode;
  },
  methods: {
    pruneCache(filter) {
      for (const key in this.cache) {
        const cachedNode = this.cache[key];
        if (cachedNode) {
          const name = getComponentName(cachedNode.componentOptions);
          if (name &&!filter(name)) {
            this.pruneCacheEntry(cachedNode);
          }
        }
      }
    },
    pruneCacheEntry(vnode) {
      if (vnode) {
        // 销毁组件实例
        vnode.componentInstance.$destroy();
        this.cache[vnode.key] = null;
        remove(this.keys, vnode.key);
      }
    }
  }
};

代码解释

  • abstract: true:表明 keep-alive 是一个抽象组件,不会渲染到 DOM 中。
  • created 钩子:初始化 cache 对象和 keys 数组。
  • destroyed 钩子:销毁所有缓存的组件实例。
  • mounted 钩子:监听 include 和 exclude 的变化,根据条件清理缓存。
  • render 方法:
    • 获取第一个子组件的虚拟节点。
    • 判断是否需要缓存该组件。
    • 如果组件已缓存,从缓存中获取实例并调整缓存顺序。
    • 如果组件未缓存,将其添加到缓存中,并根据 max 属性判断是否需要移除最早的缓存。
    • 标记组件被缓存。
  • pruneCache 方法:根据过滤条件清理缓存。
  • pruneCacheEntry 方法:销毁指定的组件实例并从缓存中移除。

总结

keep-alive 通过内部的缓存机制,避免了组件的重复创建和销毁,提升了应用的性能。它通过 cache 对象和 keys 数组来管理缓存,同时影响组件的生命周期钩子。理解 keep-alive 的实现原理,有助于我们在开发中更好地使用它,优化应用的性能和用户体验。

以上就是一文详解Vue中keep-alive的实现原理的详细内容,更多关于Vue keep-alive实现原理的资料请关注脚本之家其它相关文章!

相关文章

  • vue改变数据后数据变化页面不刷新的解决方法

    vue改变数据后数据变化页面不刷新的解决方法

    这篇文章主要给大家介绍了关于vue改变数据后数据变化页面不刷新的解决方法,vue比较常见的坑就是数据(后台返回)更新了,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • Vue中使用ECharts与v-if的问题和解决方案

    Vue中使用ECharts与v-if的问题和解决方案

    在Vue项目中使用v-if指令控制ECharts图表显示时,可能会遇到图表无法正常渲染或显示错误的问题,下面这篇文章主要介绍了Vue中使用ECharts与v-if的问题和解决方案,需要的朋友可以参考下
    2024-10-10
  • Vue3+NodeJS+Soket.io实现实时聊天的示例代码

    Vue3+NodeJS+Soket.io实现实时聊天的示例代码

    本文主要介绍了Vue3+NodeJS+Soket.io实现实时聊天的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • vue中使用vue-pdf的方法详解

    vue中使用vue-pdf的方法详解

    这篇文章主要介绍了vue中使用vue-pdf的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • vue.js实例对象+组件树的详细介绍

    vue.js实例对象+组件树的详细介绍

    这篇文章主要介绍了vue.js实例对象+组件树的相关资料,需要的朋友可以参考下
    2017-10-10
  • vue全家桶-vuex深入讲解

    vue全家桶-vuex深入讲解

    这篇文章主要介绍了vue全家桶-vuex深入讲解,文章内容详细,简单易懂,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2023-01-01
  • Vue+canvas实现水印功能

    Vue+canvas实现水印功能

    实际项目中偶尔会遇到给项目页面背景加水印的需求,这篇文章主要为大家介绍了Vue使用canvas实现水印功能的示例代码,希望对大家有所帮助
    2023-07-07
  • el-table表格排序(多列排序和远程排序)

    el-table表格排序(多列排序和远程排序)

    本文主要介绍了el-table表格排序(多列排序和远程排序),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • Vue.config.js配置报错ValidationError: Invalid options object解决办法

    Vue.config.js配置报错ValidationError: Invalid options object解

    这篇文章主要给大家介绍了关于Vue.config.js配置报错ValidationError: Invalid options object的解决办法,主要由于vue.config.js配置文件错误导致的,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • Vue中的路由配置项meta使用解析

    Vue中的路由配置项meta使用解析

    这篇文章主要介绍了Vue中的路由配置项meta使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10

最新评论