Vue基础-keep-alive

keep-alive

这个组件是个特殊的组件,可以让内部的组件被缓存。

Props

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

用法

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

当组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。

在 2.2.0 及其更高版本中,activateddeactivated 将会在 <keep-alive> 树内的所有嵌套组件中触发。

主要用于保留组件状态或避免重新渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>

<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>

注意,<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。

include and exclude

2.1.0 新增

includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 逗号分隔字符串 -->
<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>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。

max

2.5.0 新增

最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

1
2
3
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>

<keep-alive> 不会在函数式组件中正常工作,因为它们没有缓存实例。

两个新的生命周期

这两个生命周期只有在keep-alive组件内才生效。

activated

  • 类型Function

  • 详细

    被 keep-alive 缓存的组件激活时调用。

    该钩子在服务器端渲染期间不被调用。

deactivated

  • 类型Function

  • 详细

    被 keep-alive 缓存的组件失活时调用。

    该钩子在服务器端渲染期间不被调用。

注意以下几点

  1. 这两个方法只有在keep-alive组件内才会被调用。不在keep-alive内的组件没有这两个生命周期。
  2. activated在第一次加载时也会调用。deactivated在页面第一次失效时也会被调用。
  3. keep-alive内的组件第一次加载会先调用mounted,再调用activated,之后再加载该页面就只调用activated
  4. 服务端渲染这两个生命周期不生效。

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

实际使用

实际开发时我们会这样使用

方式1 自定义属性判断是否缓存

1
2
3
4
<keep-alive>
<router-view v-if="$route.meta.keepAlive" :key="$route.path" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" :key="$route.path"/>

路由中添加

1
2
3
4
5
6
{
path: '/accountedit/:id',
name: 'accountedit',
component: () => import('../views/account/add.vue'),
meta: { title: '编辑账号' ,keepAlive:true}
}

方式2 根据层级摧毁缓存

摧毁相同层级的其他路由

这种方式不适用多标签

组件内添加和mounted同级

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
26
27
28
29
30
31
beforeRouteLeave(to, from, next) {
console.info("from", from);
console.info("to", to);
console.info("this.$vnode", this.$vnode);
if (from && from.meta.rank && to.meta.rank && from.meta.rank === to.meta.rank) {
if (this.$vnode && this.$vnode.data.keepAlive) {
if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache) {
if (this.$vnode.componentOptions) {
let key = this.$vnode.key == null
?
this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
:
this.$vnode.key;
const cache = this.$vnode.parent.componentInstance.cache;
const keys = this.$vnode.parent.componentInstance.keys;
if (cache[key]) {
if (keys.length) {
const index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
}
delete cache[key];
}
}
}
}
this.$destroy();
}
next();
},

对应的路由

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
[{
path: '/account',
name: 'account',
component: () => import('../views/account/index.vue'),
meta: { title: '账号列表' ,rank:1.5}
},
{
path: '/accountadd',
name: 'accountadd',
component: () => import('../views/account/add.vue'),
meta: { title: '添加账号' ,rank:2.5}
},
{
path: '/accountedit/:id',
name: 'accountedit',
component: () => import('../views/account/add.vue'),
meta: { title: '编辑账号' ,rank:2.5}
},
{
// 角色列表
path: '/role',
name: 'role',
component: () => import('../views/role/index.vue'),
meta: { title: '角色列表' ,rank:1.5}
}]

方式3 Vuex中缓存打开标签的路由,删除所有非打开路由缓存。