项目场景:
在做后台管理系统的时候,有这样一个需求: 后台的界面如下:
点击左边的菜单,会在右边的顶部生成一个个tag导航标签。当打开多个tag页时,用户可以在多个tag之间进行切换。需要在新增,修改页面切换tag时候,保留之前的信息,不进行页面的刷新。
问题描述
经过查询vue文档,可以使用keep-alive实现标签路由缓存,实现方式如下: 在路由配置的meta中添加keepAlive,如下:
{
path: '/actdebt',
component: Layout,
redirect: '/actdebt/add',
children: [
{
path: 'add',
name: 'XXX新增配置',
meta: {
keepAlive: true,
},
component: () =>
import ('@/views/bankact/actdebt/add')
}]
},
然后在页面中使用v-if做判断,并且加上key
<keep-alive>
<router-view :key="$route.fullPath" class="avue-view"
v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view class="avue-view" v-if="!$route.meta.keepAlive" />
使用上面这种方式解决了修改不同记录的缓存问题,因为不同记录的fullPath 不一样,这样的话key就会不一样。但是对于新增和修改同一条记录还是有缓存问题。例如新增一条记录保存成功后,下次在打开新增页面,还是缓存有之前的记录。修改页面也是的,修改同一条记录保存成功后,再次打开可能还是会有之前的修改数据。
解决方案:
要解决上面这种问题我想到的解决方案为:在不同的tag导航栏切换的时候,保留缓存数据。当关闭tag导航栏或者关闭页面的时候,清除缓存。
清除缓存可以在组件里面的deactivated钩子函数调用this.$destroy();但是发现下次打开这个页面的时候,新的组件不会被缓存了。
可以利用keep-alive的include,新打开标签时,把当前组件名加入到include数组里,关闭标签时从数组中删除关闭标签的组件名就可以了。Include里面的值必须和组件的name属性保持一致,如下:
但是如果我同一个组件加载了两次,一个需要缓存,一个不需要缓存。但是他们的name却是一样的,还是无法解决问题。
所以是否可以重写keep-alive源码,使include可以按照路由地址匹配,而不是按照组件的name匹配。完整的代码如下: 新建keepAlive.js文件
const _toString = Object.prototype.toString
function isRegExp(v) {
return _toString.call(v) === '[object RegExp]'
}
export function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
function getComponentName(opts) {
return this.$route.path
}
function isDef(v) {
return v !== undefined && v !== null
}
function isAsyncPlaceholder(node) {
return node.isComment && node.asyncFactory
}
function getFirstComponentChild(children) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
function matches(pattern, name) {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
return false
}
function pruneCache(keepAliveInstance, filter) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode = cache[key]
if (cachedNode) {
const name = key
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
function pruneCacheEntry(
cache,
key,
keys,
current
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
const patternTypes = [String, RegExp, Array]
export default {
name: 'keep-alive',
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created() {
this.cache = Object.create(null)
this.keys = []
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render() {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
const name = getComponentName.call(this, componentOptions)
if (!componentOptions.Ctor.options.name) {
vnode.componentOptions.Ctor.options.name
}
const { include, exclude } = this
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
const key = name
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
然后在main.js中引入该组件,使组件可以全局使用 在页面直接使用BaseKeepAlive:
<BaseKeepAlive :include="cachetags">
<router-view :key="$route.fullPath" class="avue-view" />
</BaseKeepAlive>
cachetags 方法就是新打开标签时,把当前组件名加入到include数组里,关闭标签时从数组中删除关闭标签,源码如下:
computed: {
...mapGetters(['isLock', "tagList",'isCollapse', 'website']),
cachetags(){
let list=[]
for(let item of this.tagList){
if(!validatenull(item.keepalive)&&item.keepalive){
list.push(item.value)
}
}
return list.join(',')
}
},
方法中的tagList就是导航栏当前打开相应的tag集合如下图所示 参考: https://zhuanlan.zhihu.com/p/269385782
|