vue中keepAlive的使用
<一> keepAlive的基础知识点
<1>在动态组件上使用 keep-alive
如果希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive>
元素将其动态组件包裹起来。
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
具体看vue文档中关于 keep alive部分
注意这个 <keep-alive>
要求被切换到的组件都有自己的名字,不论是通过组件的 name
选项还是局部/全局注册。
<2>关于 keep-alive
2.1.0 新增 include and exclude
include是需要缓存的组件;
exclude是除了某些组件都缓存;
include 和 exclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
<!-- 逗号分隔字符串 -->
<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 选项的键值)。匿名组件不能被匹配。
2.5.0 新增max
最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
<keep-alive> 不会在函数式组件中正常工作,因为它们没有缓存实例。
(1)Props:
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
(2)用法:
<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
在 2.2.0 及其更高版本中,activated 和 deactivated 将会在 <keep-alive> 树内的所有嵌套组件中触发。
(3)keep-alive主要用于保留组件状态或避免重新渲染
<!-- 基本 -->
<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> 要求同时只有一个子元素被渲染。
<3>关于activated 与 deactivated两个钩子
简单介绍一下在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。
文档:在 2.2.0 及其更高版本中,activated 和 deactivated 将会在 树内的所有嵌套组件中触发。
activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。
activated调用时机:
第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用:
beforeMount=> 如果你是从别的路由/组件进来(组件销毁destroyed/或离开缓存deactivated)=>mounted=> activated 进入缓存组件 => 执行 beforeRouteEnter回调
因为组件被缓存了,再次进入缓存路由/组件时,不会触发这些钩子:// beforeCreate created beforeMount mounted 都不会触发。
deactivated:组件被停用(离开路由)时调用
使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。
这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,你可以放在这个钩子里。
如果你离开了路由,会依次触发:
组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)
<二> 在项目的具体应用
在开发中经常有从列表跳到详情页,然后返回详情页的时候需要缓存列表页的状态(比如滚动位置信息),这个时候就需要保存状态,要缓存状态;vue里提供了keep-alive组件用来缓存状态。
可以用以下两种方案解决问题;
一、利用meta标签
直接上代码
1、首先在路由中的meta标签中记录keepAlive的属性为true
path: '/classify',
name: 'classify',
component: () => import('@/views/classify/classify.vue'),
meta: {
title: '雷石淘券券',
keepAlive: true
}
},
2、在创建router实例的时候加上scrollBehavior方法
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return {
x: 0,
y: 0
}
}
}
})
3、在需要缓存的router-view组件上包裹keep-alive组件
<keep-alive>
<router-view v-if='$route.meta.keepAlive'></router-view>
</keep-alive><router-view v-if='!$route.meta.keepAlive'></router-view>
4、由于有些情况下需要缓存有些情况下不需要缓存,可以在缓存的组件里的路由钩子函数中做判断
beforeRouteLeave (to, from, next) {
this.loading = true
if (to.path === '/goods_detail') {
from.meta.keepAlive = true
} else {
from.meta.keepAlive = false
// this.$destroy()
}
next()
},
支持可以支持组件的缓存,但是这种方法有bug,首先第一次打开页面的时候并不缓存,即第一次从列表页跳到详情页,再回来并没有缓存,后面在进入详情页才会被缓存
并且只会缓存第一次进入的状态,不会重新请求数据,如果当页面A选中一个分类跳到B页面,再从B列表页面跳往详情页,此时会缓存这个状态,并且以后再从A页面的其他分类跳到B页面都不会重新被缓存,以至于每次从详情页返回B页面都会跳第一次缓存的状态;当你的项目只有一种状态需要缓存,可以考虑使用这种方法
二、使用include、exclude属性和beforeRouteLeave钩子函数和vuex (推荐使用)
这种方法将需要缓存的组件保存到全局变量 (在vuex中保存数据),可以在路由的钩子函数里灵活的控制哪些组件需要缓存,那些不需要缓存
上代码
1、在创建的router对象上加scrollBehavior方法,同上;
使用scrollBehavior , 主要针对移动端开发
2、将需要缓存的组件加在include属性里
<keep-alive :include="catch_components">
<router-view></router-view>
</keep-alive>
3、在store里加入需要缓存的的组件的变量名,和相应的方法;
const state = {
catch_components: []
}
// 异步数据
const actions = {
// 增加路由
addRoute ({
commit,
rootState
}, data) {
const ArrList = rootState.keepAlive.catch_components
let common = false
ArrList.forEach(item => {
if (item === data) {
common = true
}
})
if (!common) {
ArrList.push(data)
}
commit('get_catch', ArrList)
},
// 移除指定路由
removeRoute ({
commit,
rootState
}, data) {
const ArrList = rootState.keepAlive.catch_components
let index = null
ArrList.forEach((item, num) => {
if (item === data) {
index = num
}
})
if (index) {
ArrList.splice(index, 1)
}
commit('get_catch', ArrList)
},
// 移除所有路由
removeAllRoute ({
commit,
rootState
}, data) {
commit('get_catch', [])
}
}
// 同步数据
const mutations = {
get_catch (state, data) {
state.catch_components = data
}
}
const getters = {
get_catch: state => state.catch_components
}
export default {
state,
mutations,
getters,
actions
}
4、在需要缓存的组件的beforeRouteLeave钩子函数里控制需要缓存的组件
<1>采用vuex的辅助函数方式引入mapActions
import { mapActions } from 'vuex'
<2>给组件加name属性
因为采用vue提供的include属性,必须要求组件有name属性,否则无法匹配到组件
export default {
name:'authDetail',
components: {
},
}
<3>在methods中把mapActions中的函数展开出来
methods: {
...mapActions(['addRoute', 'removeAllRoute', 'removeRoute']),
}
<4>配置beforeRouteLeave组件单独的路由钩子,传入对应的组件name属性为参数,以此使用include属性动态控制组件添加keepAlive,以此达到控制缓存的目的
beforeRouteLeave (to, from, next) { // //要在离开该组件的时候控制需要缓存的组件,否则将出现第一次不缓存的情况
if (to.path) { // 去往详情页的时候需要缓存组件,其他情况下不需要缓存
this.addRoute('authDetail') // authDetail为需要缓存的组件的名字,include会动态匹配,添加keepAlive,这里相对于是给vuex的actions异步传参数,类似this.$store.dispatch('xxx' , 传参data数据)
}
next()
},
// 如果仅需要在此组件缓存,离开该组件后 , 需要清空缓存,让页面重新调接口重新加载数据 , 则需要在离开之前清空所有缓存,由于采用include数组的方式,所有只需要清空该组数里的name数据即可 , 直接调用方法即可,不传参数
methods:{
// 返回
onClickLeft () {
this.removeAllRoute()
this.$router.go(-1)
},
}
<5>三个解释
addRoute ({
commit,
rootState
}, data) {
const ArrList = rootState.keepAlive.catch_components
let common = false
ArrList.forEach(item => {
if (item === data) {
common = true
}
})
if (!common) {
ArrList.push(data)
}
commit('get_catch', ArrList)
},
思路是
遍历vuex里面的state数据仓库
如果通过this.addRoute('页面name')传参数进来
如果原来的state里面是存在这个数据的,说明有缓存,这个时候就不用加keepalive
如果没有的话,即!common,即把他加入到数组中
// 注意 actions里面的写法
actions:{
addRoute ({ commit,rootState}, data) {
const ArrList = rootState.keepAlive.catch_components
let common = false
ArrList.forEach(item => {
if (item === data) {
common = true
}
})
if (!common) {
ArrList.push(data)
}
commit('get_catch', ArrList)
},
}
// 解析
// 定义一个addRote方法 一般里面的参数是context,但这里细化了处理,直接使用commit作为参数,其实本质也是comtext,因为comit是可以通过context.commit点出来,context是个对象,所以这里面直接写了{commit}
// 关于rootState,它是表示根store,意思是如果vuex里面的store使用modules来划分不同的子store,那这个rootState就相当于能拿到每一个子store里面的State的数据,他是一个根数据仓库
在vuex的文档中
actions对象分发分为以下两种映射方式 一个为数组 一个为对象
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}