keep-alive的缓存与清除

315 阅读3分钟

引言

有这么一个需求:列表页进入详情页后,切换回列表页,需要对列表页进行缓存,如果从首页进入列表页,就要重新加载列表页。

对于这个需求,我的第一个想法就是使用keep-alive来缓存列表页,列表和详情页切换时,列表页会被缓存;从首页进入列表页时,就重置列表页数据并重新获取新数据来达到列表页重新加载的效果。

但是,这个方案有个很不好的地方就是:如果列表页足够复杂,有下拉刷新、下拉加载、有弹窗、有轮播等,在清除缓存时,就需要重置很多数据和状态,而且还可能要手动去销毁和重新加载某些组件,这样做既增加了复杂度,也容易出bug。

keep-alive有3个属性

include - string | RegExp | Array。只有名称匹配的组件会被缓存。  
exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。  
max - number | string。最多可以缓存多少组件实例。

从include描述来看,我发现include是可以用来清除缓存,做法是:将组件名称添加到include里,组件会被缓存;移除组件名称,组件缓存会被清除。根据这个原理,用hook简单封装一下代码:

import { ref, nextTick } from 'vue'  
  
const caches = ref<string[]>([])  
  
export default function useRouteCache () {  
  // 添加缓存的路由组件  
  function addCache (componentName: string | string []) {  
    if (Array.isArray(componentName)) {  
      componentName.forEach(addCache)  
      return  
    }  
      
    if (!componentName || caches.value.includes(componentName)) return  
  
    caches.value.push(componentName)  
  }
  // 移除缓存的路由组件  
  function removeCache (componentName: string) {  
    const index = caches.value.indexOf(componentName)  
    if (index > -1) {  
      return caches.value.splice(index, 1)  
    }  
  }  
    
  // 移除缓存的路由组件的实例  
  async function removeCacheEntry (componentName: string) {      
    if (removeCache(componentName)) {  
      await nextTick()  
      addCache(componentName)  
    }  
  }  
    
  return {  
    caches,  
    addCache,  
    removeCache,  
    removeCacheEntry  
  }  
}

hook的用法如下:

<router-view v-slot="{ Component }">  
<keep-alive :include="caches">  
<component :is="Component" />  
</keep-alive>  
</router-view>  
  
<script setup lang="ts">  
import useRouteCache from './hooks/useRouteCache'  
const { caches, addCache } = useRouteCache()  
  
<!-- 将列表页组件名称添加到需要缓存名单中 -->  
addCache(['List'])  
</script>

清除列表页缓存如下:

import useRouteCache from '@/hooks/useRouteCache'  
  
const { removeCacheEntry } = useRouteCache()  
removeCacheEntry('List')

此处removeCacheEntry方法清除的是列表组件的实例,'List' 值仍然在 组件的include里,下次重新进入列表页会重新加载列表组件,并且之后会继续列表组件进行缓存。

列表页清除缓存的时机

进入列表页后清除缓存

在列表页路由组件的beforeRouteEnter勾子中判断是否是从其他页面(Home)进入的,是则清除缓存,不是则使用缓存。

defineOptions({  
  name'List1',  
  beforeRouteEnter (to: RouteRecordNormalized, from: RouteRecordNormalized) {  
    if (from.name === 'Home') {  
      const { removeCacheEntry } = useRouteCache()  
      removeCacheEntry('List1')  
    }  
  }  
})

这种缓存方式有个不太友好的地方:当从首页进入列表页,列表页和详情页来回切换,列表页是缓存的;但是在首页和列表页间用浏览器的前进后退来切换时,我们更多的是希望列表页能保留缓存,就像在多页面中浏览器前进后退会缓存原页面一样的效果。但实际上,列表页重新刷新了,这就需要使用另一种解决办法,点击链接时清除缓存清除缓存

点击链接跳转前清除缓存

在首页点击跳转列表页前,在点击事件的时候去清除列表页缓存,这样的话在首页和列表页用浏览器的前进后退来回切换,列表页都是缓存状态,只要当重新点击跳转链接的时候,才重新加载列表页,满足预期。

// 首页 Home.vue

<li>
  <router-link to="/list" @click="removeCacheBeforeEnter">列表页</router-link>
</li>


<script setup lang="ts">
import useRouteCache from '@/hooks/useRouteCache'

defineOptions({
  name: 'Home'
})

const { removeCacheEntry } = useRouteCache()

// 进入页面前,先清除缓存实例
function removeCacheBeforeEnter () {
  removeCacheEntry('List')
}
</script>