手撸一个vueKeepAlive组件,解决参数路由无法正确缓存的问题

1,067 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情 >>

引子

在项目开发过程中,我们经常会遇到要求在系统内实现多页签来模拟浏览器的多页签效果,还需要对页面进行keepalive,保持数据状态,vue原生的keepalive组件条件是meta里的keepalive属性的话,需要在routerbeforeEach的时候手动处理meta标签的keepalive状态来清理组件缓存和建立缓存,而且针对参数路由的情况匹配的不是很好,为了解决上面提到的问题,所以决定参考keepalive的实现,去写一个自己的keepalive,当然大部分代码逻辑都是keepalive源码里已经存在的,我们只是更改一下,让他更适配我们自己的需求。

原理

首先,keep-avlie本身就是Vue内置的一个官方抽象组件,用他包裹组件的时候,不活动的实例会被缓存而不是销毁,这也是为什么keepAlive触发的事件是actived 和 deactived

属性:

  • include定义缓存组件列表,keep-alive会缓存命中的组件;
  • exclude定义排除组件列表,被命中的组件将不会被缓存;
  • max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据

LRU 最近少使用策略,从内存中找出最久未使用的数据置换新的数据.算法根据数据的历史访问记录来进行淘汰数据,其核心思想是如果数据最近被访问过,那么将来被访问的几率也更高

抽象组件

抽象组件是vue组件的一个特性,表示该组件不存在于node节点,也不存在于dom上,也就是说,他既不会被渲染为vnode或dom,也不能通过this.$parent 访问到,具体方法是在组件上声明abstract为true

export default {
  name: 'QyKeepAlive',
  abstract: true,
  ...
}

抽象组件不会被渲染的原因

抽象组件不被渲染的原因是因为,在vue的源码core/instance/liftcyle.ts中,initLifecycle这里,判断有父级,并且自身不是抽象元素时,通过while 找到不是抽象元素的父级作为父级。(这句话很绕口,相信你们能看懂的对吧?) 在渲染时,因为vnode不包含抽象组件,那真实DOM自然也不会渲染抽象组件

*<keep-alive>、<transition>、<transition-group>*这些都是抽象组件

image.png

缓存实现

keep-alive的核心是他的render函数,因为keep-alive要求只能有一个子元素被渲染,所以开头就直接获取了keep-alive下的第一个子元素,获取组件key和name,判断在不在缓存组件列表,在不在排除组件列表,下面的excludeKeys先忽略掉,那个是我们加的针对当前路由元信息判断是否进行缓存的逻辑,不重要。

image.png 下面是改写了判断缓存是否存在,以及拼接缓存key,缓存存在的时候,因为命中了缓存,所以要将缓存先移除掉,然后置于数组尾部,以这样的方式来更新访问顺序,便于进行LRU优化。否则,建立缓存,并且判断是否超过了最大缓存,如果超过,则移除数组第一个。这里注意,假如不设置,则max为无限大。

image.png 接下来讲我们的excludeKeys,excludeKeys是作为props被传入的,这里就是通过路由元信息的cacheAble来判断是否需要缓存。

 loadCacheConfig (routes, pCache = true) {
      routes.forEach(item => {
        const cacheAble = item.meta?.page?.cacheAble ?? pCache ?? true
        if (!cacheAble) {
          this.excludeKeys.push(new RegExp(`${item.path}\\d+$`))
        }
        if (item.children) {
          this.loadCacheConfig(item.children, cacheAble)
        }
      })
    }

需要清除缓存的情况

  • 点击多页签上的刷新时,需要清除缓存,通过传入clearCaches实现
  • 关闭页签时,需要清理缓存
  • 页签右键操作关闭其他及关闭全部时,需要清理缓存
<qy-keep-alive
          :exclude-keys="excludeKeys"
          v-model="clearCaches"
        >
          <router-view
            v-if="!refreshing"
            ref="tabContent"
            :key="$route.path"
          />
        </qy-keep-alive>

这里采用的是v-model的形式,我们需要在keep-alive内定义model,指定属性和自定义事件,来触发双向更新,清理缓存后,将当前缓存置空

 model: {
    prop: 'clearCaches',
    event: 'clear'
  },

image.png

为什么可以缓存?

核心在这下图这句代码。

image.png

下图是vue源码里node_modules\vue\src\core\vdom\create-component.ts中的代码,假如实例已存在,且没被销毁,且keepalive,则直接会使用当前的vnode,并且不会触发mounted事件.

image.png