在使用keep-alive缓存组件之前,需要了解组件的生命周期变化,下面列举主要的生命周期函数
// Home.vue
beforeRouteEnter: (to, from, next) => {
console.log('Home beforeEnter')
next()
},
beforeRouteLeave (to, from, next) {
console.log('Home beforeRouteLeave')
next()
},
created() {
console.log('Home created')
},
mounted() {
console.log('Home mounted')
},
destroyed() {
console.log('Home destroyed')
}
// Me.vue
beforeRouteEnter: (to, from, next) => {
console.log('Me beforeEnter')
next()
},
beforeRouteLeave (to, from, next) {
console.log('Me beforeRouteLeave')
next()
},
created() {
console.log('Me created')
},
mounted() {
console.log('Me mounted')
},
destroyed() {
console.log('Me destroyed')
}
当首次进入Home时,生命周期变化
Home beforeEnter
Home created
Home mounted
Home跳转至Me(Me创建完成 ,Home销毁,Me挂载)
Home beforeRouteLeave
Me beforeEnter
Me created
Home destroyed
Me mounted
从Me返回Home(Home组件被重新创建、挂载)
Me beforeRouteLeave
Home beforeEnter
Home created
Me destroyed
Home mounted
组件间跳转时,组件会被重新创建、挂载、销毁,有时候我们并不希望某个组件被销毁,例如首页。通常我们会在首页加载很多信息(商品、列表等等),如果不缓存首页,每次回到首页就需要重新创建组件,重新请求接口,造成页面空白,这是很影响用户体验的
keep-alive
keep-alive用于缓存组件,被缓存的组件在跳转将不会被销毁,并且还多了两个生命周期函数 activated 和 deactivated
在配置路由时为需要缓存的路由添加元信息
// router/index.js
routes: [
{
path: '/home',
name: 'home',
component: Home,
meta: { title: '首页', keepAlive: true }
},
{
path: '/me',
name: 'me',
component: Me,
meta: { title: '个人中心' }
}
]
// App.vue
<template>
<div>
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
</div>
</template>
// Home.vue 新增两个生命周期函数
activated() {
console.log('Home activated')
},
deactivated () {
console.log('Home deactivated')
}
当首次进入Home时,生命周期变化
Home beforeEnter
Home created
Home mounted
Home activated // 新增
Home跳转至Me(Home并不触发destroyed)
Home beforeRouteLeave
Me beforeEnter
Me created
Me mounted
Home deactivated // 新增
从Me返回Home(Home组件不会重新创建)
Me beforeRouteLeave
Home beforeEnter
Me destroyed
Home activated // 新增
关于缓存组件的思考
缓存组件使用的场景很多,例如上面提到的首页组件缓存(需要加载大量数据),另一个使用场景是多页面的资料填写
例如有Me,FormA,FormB三个页面(进入顺序为Me - FromA - FormB),从Me进入FormA,在FormA中填写完资料再进入FormB,FormB填写完并提交(将FormA中数据一起提交,可通过路由传参或者vuex获取FormA的数据),提交成功返回Me。
在FormB中填写到一半,突然想修改FormA中的内容,此时返回FormA,如果FormA没有使用keep-alive缓存,FormA中填写的数据将被清空(因为组件销毁重新创建了)
可以使用上面提到的方法,在FormA的路由元信息中添加keepAlive: ture,这样就可以避免FormA页面数据因为跳转而消失,但是,这样设置的组件将一直被缓存,无法销毁,我们希望在FormB提交成功后销毁FormA,或FormA返回Me时销毁FormA,这是可以实现的,使用keep-alive提供的include、exclude属性
include、exclude
include
- 字符串 / 正则表达式 / 数组。只有名称匹配的组件会被缓存。exclude
- 字符串 / 正则表达式 / 数组。任何名称匹配的组件都不会被缓存。
结合vuex,我们可以在进入FormA时将formA添加到缓存数组中,需要销毁的地方直接将它从缓存数组中除去就好了
// App.vue
<template>
<keep-alive :include="whiteList">
<router-view />
</keep-alive>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "App",
computed: {
...mapState(["whiteList"])
}
}
</script>
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
whiteList: ['home'], // 存放需要缓存的页面路径
currentHistoryLength: 0 // 记录history.length,便于通过this.$router.go()返回指定页面
},
mutations: {
// 添加缓存页面
pushPath (state, payload) {
state.whiteList.push(payload.path)
},
// 删除缓存页面
popPath (state, payload) {
let delIndex = state.whiteList.findIndex((item) => {
return item === payload.path
})
state.whiteList.splice(delIndex, 1)
},
setCurrentHistoryLength (state, payload) {
state.currentHistoryLength = payload.length
},
resetCurrentHistoryLength (state) {
state.currentHistoryLength = 0
}
}
})
Me进入FormA时,记录当前Me的history.length(便于提成成功后返回到Me)
// me.vue
export default {
name: "me",
data() {
return {
}
},
beforeRouteLeave (to, from, next) {
// 从Me返回Home,需要将store中的setCurrentHistoryLength设为0
if (to.name === 'home') {
this.$store.commit({ type: 'setCurrentHistoryLength', length: 0 })
next()
}
else {
next()
}
},
methods: {
handleApplay() {
this.$store.commit({ type: 'setCurrentHistoryLength', length: window.history.length )
this.$router.push('formA')
}
}
}
进入FormA,将FormA设置为被缓存的组件
// formA.vue
export default {
name: "formA",
data() {
return {
}
},
beforeRouteEnter (to, from, next) {
next(vm => {
// 从Me进入FormA,添加缓存路径
if (to.name === 'formA' && from.name === 'me') {
vm.$store.commit({ type: 'pushPath', path: 'formA' })
}
})
},
beforeRouteLeave (to, from, next) {
// FormA返回Me,删除缓存路径
if (to.name === 'me') {
this.$store.commit({ type: 'popPath', path: 'formA' })
next()
}
else {
next()
}
}
}
在FormB提交成功后,删除formA缓存并返回到Me
// formB.vue
export default {
name: "formB",
data() {
return {
}
},
methods: {
handleSubmit () {
// 请求操作~
let backStep = window.history.length - this.$store.state.currentHistoryLength
this.$router.go(-backStep)
this.$store.commit({ type: 'popPath', path: 'formA' })
this.$store.commit({ type: 'setCurrentHistoryLength', length: 0 })
}
}
}