Vue匿名组件使用keep-alive后动态清除缓存
在使用Vue开发管理系统项目的时候,为了保存页面的浏览状态,我们可以使用内置组件keep-alive
来缓存组件内部状态,避免重新渲染。
<keep-alive>
<router-view></router-view>
</keep-alive>
被keep-alive包裹的动态组件或router-view会缓存不活动的实例,再次被调用这些被缓存的实例会被再次复用,而不需要再次发送HTTP请求。对于使用tabs标签页打开页面时,这正是我们想要的效果。但是这样做同时也存在一个问题,就是被keep-alive包裹的组件会保持最后一次请求数据的渲染结果,即使我们关闭了tabs页,再次打开时依旧是上一次的状态。这时,我们就得用上include、exclude属性来对缓存的组件进行筛选。
Props:
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
<!-- 逗号分隔字符串 --> <keep-alive include="a,b"> <component :is="view"></component> </keep-alive> <!-- 正则表达式 (使用 `v-bind`) --> <keep-alive :include="/a|b/"> <component :is="view"></component> </keep-alive> <!-- 数组 (使用 `v-bind`) --> <keep-alive :include="['a', 'b']"> <component :is="view"></component> </keep-alive>
有了include和exclude对组件进行筛选,然后动态的记录tabs中打开的组件名,这就完美的解决了动态缓存的问题。所以在配置路由文件时,我们必须得先import组件,然后以组件名调用。
import HelloWorld from '@/components/HelloWorld'
routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }
但是,在项目开发中,为了提升页面加载的速度,一般会采用路由懒加载的方式加载路由组件,这时调用的组件不再是通过组件名,而是匿名组件。
{ path: '/home', name: 'home', component: resolve => require(['@/components/learn/home'], resolve) }
可是,官方文档指出:
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
这样一来include和exclude就无效了。。。
额(⊙o⊙)…目前我们项目就是这样通过匿名组件调用的,所以就没有使用keep-alive组件。这时领导试用系统,咦...我这打开的页面怎么每次点进去都重置了页面内容,怎么不能像打开网页那样,我看到哪就给保存那时的状态呢,不过由于大家说弄不了,就把这个功能推掉了。
但是这段时间正好手中的事告一段落了,有空余时间,并本着有问题就得解决的原则,就自己琢磨起来。首先使用keep-alive进行全站缓存这个是肯定不行的,使用者肯定得说:怎么我把页面关了打开后还是之前的呢。。。所以,感觉这是一个很复杂的问题,要不然早就加上了。
于是乎,打开度娘寻找心灵安慰。找吖找吖找,也不知在清一色的复制粘帖中摸索了多久,除了使用include来解决动态缓存问题,完全没有对匿名组件的解决方案。就在快要放弃和度娘交流时,突然眼前闪过一丝希望的光芒!
终于发现了大神研究出的可行方案:Vue 全站缓存之 keep-alive : 动态移除缓存 https://juejin.im/post/5b610da4e51d45195c07720d。
在茫茫代码中,他一眼就看到了隐藏在角落里的vue-component-12-keep-alive,然后才有了完美的解决方案。那就是使用Vue.mixin的方法拦截了路由离开事件,并在该拦截方法中实现了销毁页面缓存的功能。
// 全局混入,关闭tab时清除组件缓存 Vue.mixin({ beforeRouteLeave(to, from, next) { let flag = true this.$store.state.options.forEach(e => { // options存储打开的tabs的组件路由 if(from.path == e.route) { flag = false } }) if(flag && this.$vnode.parent && this.$vnode.parent.componentInstance.cache) { let key = this.$vnode.key // 当前关闭的组件名 let cache = this.$vnode.parent.componentInstance.cache // 缓存的组件 let keys = this.$vnode.parent.componentInstance.keys // 缓存的组件名 if(cache[key] != null) { delete cache[key] let index = keys.indexOf(key) if(index > -1) { keys.splice(index, 1) } } } next() } })
不过在这之前我们还得解决一个问题,由于此方法是拦截了路由离开事件,而当我们关闭不是当前激活的标签页时是不会触发路由离开事件的,这就会导致清除该缓存失效,所以我们得在关闭不激活的标签页时先模拟一次点击事件才能达到预期的效果。
// 移除tab标签 tabRemove (targetName) { let event = new Event('click') document.getElementById("tab-" + targetName).dispatchEvent(event) // 触发点击事件 this.$store.commit('delete_tabs', targetName); if (this.activeIndex === targetName) { // 设置当前激活的路由 if (this.openTab && this.openTab.length >= 1) { this.$store.commit('set_active_index', this.openTab[this.openTab.length - 1].route); this.$router.push({ path: this.activeIndex }); } else { this.$router.push({ path: '/' }); } } }
OK,这样就完美了解决了匿名组件动态清除缓存的问题了。