keep-alive 之 router-view

791 阅读2分钟

前言

用户从列表页 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'
  }
}

但是呢,实码测试发现,这个方案有一个致命的缺陷。

我们来演示一下:首先我们在 pageAdata 里定义一个 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 缓存的销毁实际上是和它的 includeexclude 密切相关的,所以,我们其实是可以从 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 以达到更新缓存的目的。

几点注意

  1. 被缓存的页面组件一定要配置 name 属性,router.js 里的 name 仅供路由用的,并不是页面组件的命名。
  2. beforeRouteEnter 钩子函数里是拿不到 this 的,因为此时组件实例还没被创建,此时只能在 next 回调中拿到实例