前言
用户从列表页 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
回调中拿到实例