一、keep-alive 组件介绍
keep-alive 是vue的内置组件,作用是缓存不活动的组件实例,在切换组件时将状态保留在内存(vuex)中,防止重新渲染dom,减少页面加载以及性能消耗,同时提高用户体验性。
该组件有三个 props 属性:
-
include:字符串、正则表达式或数组;只有名称(name)匹配的 vue组件 会被缓存
-
exclude:字符串、正则表达式或数组;任何名称(name)匹配的 vue组件 都不会被缓存
-
max:数字;表示这个 keep-alive 最多可缓存多少个组件实例;缓存的组件实例达到这个最大值之后,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉
注: 1、上述的 name 指的是 vue组件 中的 name,而不是路由router中的name! 2、匹配时,首先匹配组件自身的 name 值,若 name 不可用,则匹配 它的局部注册名称(父组件 components 中的键值);若两者皆没有,则为匿名组件,匿名组件不能被匹配 3、匹配时,name 区分大小写!
Vue.js 官网上的使用案例:
<!-- 逗号分隔字符串 -->
<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>
二、项目中的实际使用
1、keep-alive 使用的逻辑步骤
-
首先,在添加 路由 的时候,定义一个属性,用于动态判断该组件是否需要缓存;此处项目中利用meta中的 noCache 属性来判断,noCache 的值为 true时,表明该组件不需要缓存;noCache 的值为 false 时,表明该组件需要缓存
const mciFunctionRoutes = { path: ‘/function‘, component: Layout, meta: { title: ‘业务功能‘ }, children: [ { path: ‘projectManage‘, component: PlaceholderView, meta: { title: ‘项目管理‘, noCache: true }, children: [ { path: ‘myProject‘, component: () => import(‘@/views/mci/projectManage/myProject‘), name: ‘myProject‘, meta: { title: ‘我的项目‘, noCache: false } }, { path: ‘projectCountView‘, component: () => import(‘@/views/mci/projectManage/projectCountView‘), name: ‘projectCountView‘, meta: { title: ‘项目统计视图‘, noCache: true } } ] } ] }
在创建router实例的时候,还需加上 scrollBehavior 方法,如下:
const router = new Router({ mode: ‘history‘, routes: constantRoutes.concat(asyncRoutes), scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } } });
-
在 vuex 中存储需要缓存的组件实例,可以存储它们的 name(这就要求每个组件的 name 值都是唯一的);此处存储的 name 是router路由中的name,而 keep-alive 中使用的 又是 组件的 name 名称,所以 此处有个潜规则,就是router中定义的name 需要与 组件内部定义的 name 值一致
state: { cachedViews: commonUtil.decodeQuery(sessionStorage.getItem(‘cachedViews‘)) || [] // 只存储路由name(不会始终包含常驻标签) }, mutations: { // 根据 路由中的meta的 noCache 属性,动态判断该组件是否需要缓存 ADD_CACHED_VIEW: (state, view) => { if (state.cachedViews.includes(view.name)) return; if (!view.meta.noCache) { let cachedViews = state.cachedViews; cachedViews.push(view.name); sessionStorage.setItem(‘cachedViews‘, commonUtil.encodeQuery(cachedViews)); state.cachedViews = cachedViews; } }, DEL_CACHED_VIEW: (state, view) => { for (const i of state.cachedViews) { if (i === view.name) { const index = state.cachedViews.indexOf(i); let cachedViews = state.cachedViews; cachedViews.splice(index, 1); sessionStorage.setItem(‘cachedViews‘, commonUtil.encodeQuery(cachedViews)); state.cachedViews = cachedViews; break; } } sessionStorage.setItem(‘cachedViews‘,commonUtil.encodeQuery(state.cachedViews)); } }, actions: { addCachedView({commit}, view) { commit(‘ADD_CACHED_VIEW‘, view) }, delCachedView({commit, state}, view) { return new Promise(resolve => { commit(‘DEL_CACHED_VIEW‘, view); resolve([...state.cachedViews]) }) } }
-
在需要存储 需要缓存的组件实例 时,通过 dispatch 调用 vuex中的 addCachedView 方法,存储对应路由的 name值
this.$store.dispatch(‘addCachedView‘, this.$route);
-
在需要缓存的 router-view 组件上包裹 keep-alive 组件,在 keep-alive 中,给 include 赋值需要缓存的组件实例即可;此处设置最多可缓存 10 个vue组件实例
<keep-alive :include="$store.state.tagsbar.cachedViews" :max="10"> <router-view /> </keep-alive>
-
在组件的 name 已经存在于 vuex中的cachedViews数组中,但该组件不需要再缓存时,只需要移除 cachedViews 中的该元素即可实现重新渲染加载该组件;此处使用 dispatch 调用 delCachedView 方法即可
this.$store.dispatch(‘delCachedView‘, this.$route);
2、嵌套的router-view,都要添加keep-alive
因为 keep-alive 组件是用在一个直属的子组件被开关的情形,所以 当项目中存在 组件嵌套关系 时,则 keep-alive 的缓存将不起作用。例如:
// mciFunctionRoutes 路由文件中
const mciFunctionRoutes = {
path: ‘/function‘,
component: Layout,
meta: {
title: ‘业务功能‘
},
children: [
{
path: ‘projectManage‘,
component: PlaceholderView,
meta: { title: ‘项目管理‘, noCache: true },
children: [
{
path: ‘myProject‘,
component: () => import(‘@/views/mci/projectManage/myProject‘),
name: ‘myProject‘,
meta: { title: ‘我的项目‘, noCache: false }
},
{
path: ‘projectCountView‘,
component: () => import(‘@/views/mci/projectManage/projectCountView‘),
name: ‘projectCountView‘,
meta: { title: ‘项目统计视图‘, noCache: true }
}
]
}
]
}
// index.js 路由文件中
export const constantRoutes = [
{
path: ‘/‘,
component: Layout,
redirect: ‘dashboard‘,
children: [
{
path: ‘dashboard‘,
component: () => import(‘@/views/dashboard/index‘),
name: ‘dashboard‘,
meta: {title: ‘首页‘, noCache: true, affix: true}
}
]
}, {
...mciFunctionRoutes
}
]
在 index.js 文件中,dashboard 页面(首页)相当于直接注册在 Layout 页面中的 router-view 组件上,而 myProject 页面(我的项目)相当于注册在 PlaceholderView 页面中 的 router-view 组件上;而 PlaceholderView 组件则相当于注册在 Layout 页面中的 router-view 组件上,类似 父子关系。
在这种情况下,若只在 layout.vue 文件中添加 keep-alive 组件,则会导致 myProject 页面(我的项目)缓存不成功,还需在 PlaceholderView.vue 文件中同时添加 keep-alive 组件才可,如下:
// layout.vue 文件中
<keep-alive :include="$store.state.tagsbar.cachedViews" :max="10">
<router-view />
</keep-alive>
// placeholerView.vue 文件中
<keep-alive :include="$store.state.tagsbar.cachedViews" :max="10">
<router-view />
</keep-alive>
3、beforeRouteEnter 和 beforeRouteLeave
一般可用其处理 某些页面的动态缓存,比如,当从a页面跳转到b页面时,则设置 b页面 不加入缓存,从其他页面跳转到b页面时,则设置 b页面 加入缓存,如下:
// 在即将进入当前页面时调用,在 created 和 mounted 钩子函数之前调用
beforeRouteEnter(to, from, next) {
// 逻辑处理
if(from.path == "/function/businessReport/onSaleReport") {
to.meta.noCache = true;
} else {
to.meta.noCache = false;
}
next();
}
// 在即将离开当前页面时调用
beforeRouteLeave(to, from, next) {
// 逻辑处理
next();
}
注:
beforeRouteEnter 和 beforeRouteLeave 两个钩子必须写在有配置路由的页面上才有效!
4、手动销毁被keep-alive缓存的实例
在关闭某标签页时,已经将该组件的name从 缓存数组 中移除,但该缓存实例仍未被销毁,即可使用以下 混入对象,以下代码中的watch即可监听到 visitedViews 的变化,从而手动销毁不在其中的实例。
const destoryCompMixin = {
data() {
return {
routePath: ‘‘
}
},
mounted() {
this.routePath = this.$route.path
},
computed: {
visitedViews() {
return this.$store.state.tagsbar.visitedViews
}
},
watch: {
visitedViews(value) {
let flag = value.some(item => {
return this.routePath == item.path;
})
if(!flag) {
this.$destroy(this.$options.name)
}
}
}
}
export default destoryCompMixin;
5、activated 和 deactivated
只有使用了 keep-alive 包裹的组件有这两个生命周期,activated 的作用 同 mounted,在页面初始化时加载,但由于使用了 keep-alive缓存 之后,再次回到页面不会执行mounted钩子函数,但会执行 activated 钩子函数,所以 初始化查询 等操作可放在 activated 中。
当vue组件在keep-alive中被切换时,这个组件的 activated 和 deactivated 两个生命周期钩子函数将会被对应执行。
activated 在 第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用;调用顺序大致为:beforeMount=>mounted=> activated 进入缓存组件 => 执行 beforeRouteEnter回调
deactivated 可看作 beforeDestroy 的替代,因为缓存组件不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁);调用顺序大致为:beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)
6、使用 keep-alive 时,路由层级不同的组件之间跳转,缓存会失效
解决方案待学习研究......