我正在参与创作者训练营第6期,点击了解活动详情
问题描述
前几天笔者遇到这样一个问题,在项目中使用keep-alive
缓存路由,路由是以页签那种方式打开的,当页签切换,我们需要路由缓存,当页签关闭,期望keep-alive
中缓存的组件要销毁。问题就是页签关闭了,再次打开相同的路由页签时,还是走的缓存,之前的状态还在,关键还只是有些路由有问题。先看下具体的现象吧:
上图中共有两个页面,是笔者为了写这篇文章才配置的,其中 A
页面是正常的那种路由,B
面时不正常的那种路由,从上面的图不难看出来,当打开 A
页面,我在右上角输入框输入了11111
,然后关闭了路由再打开,那个11111
已经消失了,代表这个页面重新创建了,是符合我们预期的;但当打开B
页面时,我们在左上角的输入框输入了11111
,关闭再打开之后,11111
还在,这就是问题所在。
通过vue-devtools
可以很明显看到这个问题,就是B
页面的缓存还在,如下图所示:
分析问题
然后笔者就开始寻找问题,先看下缓存那里是怎么实现的吧,路由出口:
从上面可以看出:include
的值就是cacheViews
中每条路由的name
,那路由的name
又是怎么来的❓
系统中的菜单都是配置在数据库中根据权限读取的,数据库中配置了相对于views
目录的路由路径,然后在登陆后根据权限读取到菜单,再动态生成路由,路由名字为菜单ID:
在动态import
组件的时候,会把组件的name
也赋值为菜单ID:这样一来当打开路由include
中就会包含路由的name
,keep-alive
就会对其进行缓存。
keep-alive
只有当组件的名字存在include
中才会缓存。
到这呢只是把缓存怎么搞的弄明白了,问题还是没找到?
这时笔者注意到,路由出口那个Vue
组件中,计算属性cachedViews
有两个组件名是写死的:
const cvs = ['Schema', 'TemplateFactory']
然后再去看下B
页面怎么玩的,B
页面和A
页面逻辑都一样,只是component
有差别。
然后笔者就发现问题所在了,所有正常的页面都是一个页面对应一个Vue
组件,所有不正常的页面component
都是写死的这两个组件,原来这两个组件是公司低码配置出来的页面的公共入口,所以不能像一一对应的那种去更改组件的name
,只能在这里写死,是笔者前几天写的。(原来是自己挖的坑)
所以凡是这种页面,include
中的name
就相当于没有用了,自然而然当include
中这条路由的name
没了也不会触发keep-alive
删除对应组件的缓存(因为Schema
, TemplateFactory
是一直存在于include
中的),就导致了这个问题。那接下来看下怎么解决吧!
解决问题
既然keep-alive
不会帮我们清理缓存,那我们可不可以自己清理呢❓
通过调试keep-alive
的源码,我们发现有个cache
属性,上面就是缓存的组件实例,如下图:
那是不是意味着,只要我们能获取到keep-alive
的组件实例,就可以自己手动清理❓
那就是在beforeRouteLeave
这个钩子中去处理,在Schema
组件中加上这个钩子:
beforeRouteLeave(to, from, next) {
debugger
next()
},
问题又来了,并拿不到这个keep-alive
的组件实例,讲道理应该在$parent
的$parent
嘛
原来keep-alive
这个组件是一个abstract
的组件,所以keep-alive
的实例不会出现在组件的父组件链中
<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
难道就没有办法了吗❓突然笔者看到了个熟悉的字眼:
再点开,就有了:
那接下来就好办了,完整代码如下:
beforeRouteLeave(to, from, next) {
try {
const cachedViews = this.$store.state.tagsView.cachedViews
const findInCache = cachedViews.find(v => v.path === from.path)
if(!findInCache) { // 说明是关闭了路由,不是切换
const keepAliveInstance = this.findKeepAlive()
if(!keepAliveInstance) {
return next()
}
const { cache } = keepAliveInstance
const cacheKeys = Object.keys(cache)
const delKey = cacheKeys.find(key => key.includes(from.path))
delKey && (cache[delKey] = null)
Reflect.deleteProperty(cache, delKey)
this.$destroy() // 调用组件的销毁
}
next()
} catch (error) {
next();
}
},
methods: {
//...
findKeepAlive() {
const vnodeParent = this.$vnode.parent
if(vnodeParent.tag.includes('keep-alive')) {
return vnodeParent.componentInstance
}
return null
}
//...
}
✅最终也是完美的解决了问题。
✅如果掘友们有更优雅的方案,欢迎在评论区留言噢