引言
有这么一个需求:列表页进入详情页后,切换回列表页,需要对列表页进行缓存,如果从首页进入列表页,就要重新加载列表页。
对于这个需求,我的第一个想法就是使用keep-alive来缓存列表页,列表和详情页切换时,列表页会被缓存;从首页进入列表页时,就重置列表页数据并重新获取新数据来达到列表页重新加载的效果。
但是,这个方案有个很不好的地方就是:如果列表页足够复杂,有下拉刷新、下拉加载、有弹窗、有轮播等,在清除缓存时,就需要重置很多数据和状态,而且还可能要手动去销毁和重新加载某些组件,这样做既增加了复杂度,也容易出bug。
接下来说说我的想到的新实现方案(代码基于Vue3)。
keep-alive 缓存和清除
keep-alive 缓存原理:进入页面时,页面组件渲染完成,keep-alive 会缓存页面组件的实例;离开页面后,组件实例由于已经缓存就不会进行销毁;当再次进入页面时,就会将缓存的组件实例拿出来渲染,因为组件实例保存着原来页面的数据和Dom的状态,那么直接渲染组件实例就能得到原来的页面。
keep-alive 最大的难题就是缓存的清理,如果能有简单的缓存清理方法,那么keep-alive 组件用起来就很爽。
但是,keep-alive 组件没有提供清除缓存的API,那有没有其他清除缓存的办法呢?答案是有的。我们先看看 keep-alive 组件的props:
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)) returncaches.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>
<sc