前言
用户从列表页 pageA 进入到详情页 pageB ,查看或编辑完成之后再返回到列表页 pageA时,产品希望能保留列表页 pageA 当时的索引条件、分页数等。
网上流传的方案大多偏向于下面这种:
// router.js
{
path: '/pageA',
name: 'pageA',
meta: {
keepalive: true
},
component: () => import('@/views/pageA.vue')
}
// app.vue
<keep-alive>
<router-view v-if="$route.meta.keepAlive"> </router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"> </router-view>
// script
export default {
beforeRouteLeave (to, from, next) {
const pageA = [to, from].fliter(item => item.name === 'pageA')[0]
pageA.meta.keepalive = to.name === 'pageB'
}
}
但是呢,实码测试发现,这个方案有一个致命的缺陷。
我们来演示一下:首先我们在 pageA 的 data 里定义一个 time,然后在 created 方法里去初始化它,并在 pageA 模板文件里展示出来,然后,再按如下步骤操作:
// 第一次
pageC -> pageA -> pageB // pageA : 1(假设的时间戳值)
pageB -> pageA -> pageC // pageA : 1(假设的时间戳值)
// 第二次
pageC -> pageA -> pageB // pageA : 2(假设的时间戳值)
pageB -> pageA -> pageC // pageA : 1(假设的时间戳值)
// 第三次
pageC -> pageA -> pageB // pageA : 3(假设的时间戳值)
pageB -> pageA -> pageC // pageA : 1(假设的时间戳值)
...
发现问题没? pageA 的缓存是不会更新的,也就是说:
第一次,我从 pageC 进入到 pageA,然后把分页切换到 2 进入 pageB,然后返回到 pageA ,此时 pageB 展示的是第 2 页的内容这个没错。
然后,我们现在将分页值改为 3 ,进入 pageB,然后再返回到 pageA,此时 pageB 依然展示的是第 2 页的内容。
这个肯定是不得行的。
改进
看过 keep-alive 源码的掘友应该知道:
mounted () {
// 实时监听黑白名单的变动
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
}
keep-alive 缓存的销毁实际上是和它的 include 和 exclude 密切相关的,所以,我们其实是可以从 include 着手去换一种思路实现缓存。
// app.vue
<keep-alive :include="alives">
<router-view></router-view>
</keep-alive>
export default {
data () {
return {
alives: []
}
},
created () {
this.$bus.on('aliveChange', ({type, name}) => {
if (type === 'add') {
if (!this.alives.includes(name)) this.alives.push(name)
} else {
this.alives = this.alives.filter(item => item !== name)
}
})
}
}
// pageA.vue
export default {
name: 'pageA'
beforeRouteEnter (to, from, next) {
next(vm => {
vm.$bus.emit('aliveChange', { type: 'add', name: 'pageA' })
})
},
beforeRouteLeave (to, from, next) {
if (to.name !== 'pageB') {
this.$bus.emit('aliveChange', { type: 'remove', name: 'pageA' })
}
next ()
}
}
这样,我们通过动态修改 includes 以达到更新缓存的目的。
几点注意
- 被缓存的页面组件一定要配置
name属性,router.js里的name仅供路由用的,并不是页面组件的命名。 beforeRouteEnter钩子函数里是拿不到this的,因为此时组件实例还没被创建,此时只能在next回调中拿到实例