概念

  • keep-alive
    • keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和transition相似,keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中。
    • keep-alive: vue文档
  • 组件内的守卫-beforeRouteLeave

前言

后台管理系统中,左侧为功能菜单栏,点击菜单列表,右侧显示该菜单的功能页面,本来是一个非常简单的后台管理系统布局,现在增加了tabs菜单按钮;
点击左侧菜单栏时,右侧页面头部header显示当前的页面标题,形成一个tabs列表,点击可切换页面内容和关闭tab;
现在的需求:
打开之后需要保留的页面在tab不关闭的情况下,保留上一次的状态,而在tab关闭之后,则下次打开会重新请求数据,不会保留上次状态。

初步解决

第一想到的就是利用vue的功能组件

1
2
3
<keep-alive>
<router-view />
</keep-alive>

但是这种情况只能实现缓存所有页面,就是不能根据需求实现动态缓存,所有状态都存着呢,愁啊。

解决方案构想:

路由元信息内添加特定字段如:keepAlive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: {
keepAlive: true
}
}
]
}
]
})

父组件内根据路由中的keepAlive字段动态使用keep-alive标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Home extends Vue {

get keepAlive () {
// 获取当前路由的元信息中的keepAlive字段
return this.$route.meta.keepAlive
}

private render () {
return (
<div>
{
!this.keepAlive && <router-view />
}
<keep-alive>
{
this.keepAlive && <router-view/>
}
</keep-alive>
</div>
)
}
}

export default Home

思路

由于现在组件的keep-alive是动态根据路由元信息中的keepAlive字段进行动态使用的,所以只要动态改变对应路由元信息的keepAlive字段就可以实现动态缓存。

实现方案

方案一

利用beforeRouteLeave改变fromkeepAlive实现(原思路,网络解决方案之一,有bug)

1
2
3
4
5
6
7
8
9
10
11
12
beforeRouteLeave (to: any, from: any, next: any) {
// 导航离开该组件的对应路由时调用
// 判断是否是去往页面 C
if (to.name !== 'C') {
// 不是去 C 页面,不缓存
from.meta.keepAlive = false
} else {
// 是去 C 页面,缓存
from.meta.keepAlive = true
}
next()
}

bug:首次去C页面,再返回B页面,B并没有缓存,第二次再进入C页面,B页面缓存,且进A页面并不能清除B页面的缓存

方案二(网络方案)

$destroy()销毁

1
2
3
4
5
6
7
8
9
beforeRouteLeave (to: any, from: any, next: any) {
// 导航离开该组件的对应路由时调用
// 判断是否是去往页面 C
if (to.name !== 'C') {
// 不是去 C 页面,不缓存
this.$destroy()
}
next()
}

bug:销毁之后永远不会被缓存

方案三(网络方案)

  • 根据源码看来缓存的组件都会设置一个cache属性,可以通过代码强行移除掉。缺点就是没有彻底销毁依旧占内存
  • 具体实现参考

方案四(最优解)

利用keep-aliveinclude属性,利用vuex动态控制include达到动态管理缓存

1
2
3
<keep-alive :include="keepAliveList">
<router-view :key="$route.fullPath"/>
</keep-alive>

利用计算属性和vuex获取缓存列表

这里可以使用你自己的规则,原因请继续往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--vuex-->
state: {
keepAliveList:'',//保存缓存的列表
},
mutations: {
setKeepAliveLists(state,arrListString){
state.keepAliveList = arrListString;
},
}

<!--视图组件中-->

computed:{
keepAliveList(){
// 获取缓存的路由列表
return this.$store.state.keepAliveList;
}
}

生成缓存列表,列表的值为各组件中name的值集合拼接的字符串

1
this.$store.commit('setKeepAliveLists',routerComponentNameList.join())

点击左侧菜单栏时,更新缓存列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 点击左侧菜单的事件函数 -->
handleSelect(name) {
if(this.routerNameMap.has(name)){//如果当前点击的路由已经在缓存列表中,则先清除缓存列表,再添加;
this.resetKeepAive(name,this.keepAliveList);//删除缓存路由
this.tabChangeRoute(name);//切换路由
} else {
this.routerNameMap.add(name)
this.tabChangeRoute(name);
}
},
// 更新要缓存的路由列表
resetKeepAive(name,cacheList) {
const conf = this.keepAliveList;
let arr = cacheList.split(',');
if (name && typeof name === 'string') {
let i = arr.indexOf(name);
if (i > -1) {
arr.splice(i, 1);
this.$store.commit('setKeepAliveLists',arr.join());
this.$nextTick(() => {//添加缓存路由
this.$store.commit('setKeepAliveLists',conf);
})
}
}
}

点击右侧tabs关闭标签删除缓存

1
2
3
4
5
removeTab(name){
// 点击tab上的关闭按钮,清除当前路由的缓存
this.routerNameMap.delete(name);
this.resetKeepAive(name,this.keepAliveList);//删除缓存路由
}

下面为主要代码,监听当前路由是否被移除缓存,如果移除缓存则需要销毁该组件,否则内容中的缓存组件会越来越来,影响使用性能;
创建一个mixin.js文件,然后引入到需要被动态缓存的路由组件中即可;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 路由缓存管理
export default {
computed: {
keepAliveConf(){
return this.$store.state.keepAliveList;
}
},
watch:{
keepAliveConf(e){
// 监听缓存列表的变化,如果缓存列表中没有当前的路由或组件则在缓存中销毁该实例
let name = this.$options.name;
if(!e.split(',').includes(name)) {
this.$destroy()
}
}
},
}

总结

关于在$store中保存和删除的过程,各人有各人的方法,我这边只是一种参考,不过多讨论。

不过关于keep-aliveinclude原理,这里简单介绍下,官方(截止到2020年1月)给的方案是正则白名单,也就是说在include中包含的字段,只要路由的name满足正则要求,则会缓存,exclude的规则同样满足,所以这就造成匿名路由的童鞋无法使用keep-alive。不过也有解决方案,使用beforeRouteLeave监听,自己做缓存,不过目前我还没试过,后续我试完会新写的。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我的支持。😘✔