一文详解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实现原理的资料请关注脚本之家其它相关文章!

相关文章

  • element-ui和vue表单(对话框)验证提示语(残留)清除操作

    element-ui和vue表单(对话框)验证提示语(残留)清除操作

    这篇文章主要介绍了element-ui和vue表单(对话框)验证提示语(残留)清除操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • 解决vue2+vue-router动态路由添加及路由刷新后消失问题

    解决vue2+vue-router动态路由添加及路由刷新后消失问题

    这篇文章主要介绍了解决vue2+vue-router动态路由添加及路由刷新后消失问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • vue3.0 加载json的方法(非ajax)

    vue3.0 加载json的方法(非ajax)

    这篇文章主要介绍了vue3.0 加载json的方法(非ajax),帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
    2020-10-10
  • elementui导出数据为xlsx、excel表格

    elementui导出数据为xlsx、excel表格

    本文主要介绍了elementui导出数据为xlsx、excel表格,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • vue列表单项展开收缩功能之this.$refs的详解

    vue列表单项展开收缩功能之this.$refs的详解

    这篇文章主要介绍了vue列表单项展开收缩功能之this.$refs的详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Vue中使用better-scroll实现轮播图组件

    Vue中使用better-scroll实现轮播图组件

    better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。这篇文章主要介绍了Vue中使用better-scroll实现轮播图组件的实例代码,需要的朋友可以参考下
    2020-03-03
  • 详解vantUI框架在vue项目中的应用踩坑

    详解vantUI框架在vue项目中的应用踩坑

    这篇文章主要介绍了详解vantUI框架在vue项目中的应用踩坑,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • 在vue项目中使用element-ui的Upload上传组件的示例

    在vue项目中使用element-ui的Upload上传组件的示例

    本篇文章主要介绍了在vue项目中使用element-ui的Upload上传组件的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • vue业务实例之组件递归及其应用

    vue业务实例之组件递归及其应用

    目中出现多级菜单时,需要多层for循环时,但是当菜单增加层级时,需要在页面结构中增加一层for循环,这时我们可以使用组件递归的思想来实现,下面这篇文章主要给大家介绍了关于vue业务实例之组件递归及其应用的相关资料,需要的朋友可以参考下
    2022-05-05
  • vue项目关闭eslint校验

    vue项目关闭eslint校验

    eslint是一个JavaScript的校验插件,通常用来校验语法或代码的书写风格。这篇文章主要介绍了vue项目关闭eslint校验,需要的朋友可以参考下
    2018-03-03

最新评论