前言

业务里经常有这样的需求, 页面跳转的时候保持前一个页面的状态跟数据, 方便返回时处理
例如: A->B需要缓存, A->C不需要缓存
网上大部分例子都需要去处理业务组件, 感到非常不合理
故自己分享改功能用来处理此类问题

vue的keepAlive组件很适合解决这类问题, 但还是有缺陷, 比如很难灵活的根据路由来控制是否需要缓存, 只能简单的通过include来匹配是否需要缓存

整理后需求如下:

1. 组件能在部分页面缓存,脱离了这部分页面则清理缓存
2. 组件能全局缓存
3. 最好从架构层面解决,不要干涉业务组件

大致思路

在路由meta里添加keealive:['路由的name'],确定生效的作用页面
设置一个store来绑定keepalive
在全局路由钩子里对该store处理

代码如下

路由meta里设置keepAlive,表示访问这些页面需要缓存组件

// 路由配置
{
    path: 'xxx',
    name: 'xxx',
    component: () => import('xxx'),
    meta: {
        title: 'xxx',
        keepAlive: ['路由name', 'b'],//作用域:缓存在这些页面留存
    },
},

在store里设置keepAliveIncludes用来绑定keepAlive 

// vuex 设置跟删除缓存组件
state: {
    keepAliveIncludes:[]
 },
mutations: {
    // 设置缓存
    SET_KEEPALIVEINCLUDES: (state, data) => {
      const has = state.keepAliveIncludes.find(i=>i.path === data.path)
      if(!has){
        state.keepAliveIncludes.push(data)
      }
    },
    // 删除缓存
    DELETE_KEEPALIVEINCLUDES:(state, data) => {
      state.keepAliveIncludes = state.keepAliveIncludes.filter(i=>i.always)
    }
  },
// 业务页面,使用keepAlive的组件
<template>
  <keep-alive :include="keepAliveIncludes" >
    <router-view ref="content"/>
  </keep-alive>
</template>

<script>
export default {
  computed: {
    'keepAliveIncludes'() {
      const arr = this.$store.state.keepAliveIncludes.map(i=>i.componentName)
      return Array.from(new Set(arr))
    },
  }
</script>

这样就将keepAliveIncludes跟keepalive组件绑定了
因为keepAlive组件内部对include做了订阅,所以我们不用考虑怎么删除已有缓存,只需要对keepAliveIncludes这个值处理,组件内部会帮我们删除

通过全局路由钩子处理keepAliveIncludes

// 路由钩子
router.beforeEach((to, from, next) => {
    // 将所有缓存页面的keepalive拿出来扁平化
    let cachePath = store.state.keepAliveIncludes.map(i => i.keepAlive).flat(Infinity)
    // 去了不缓存的页面就清理所有非常驻缓存组件
    if (!cachePath.includes(to.name)) {
        // console.log('去了不缓存的页面');
        store.commit('DELETE_KEEPALIVEINCLUDES')
    }

    if (to.meta.keepAlive) {
        // const componentsName = to.matched[to.matched.length-1].components.default.name
        store.commit('SET_KEEPALIVEINCLUDES', {
            path: `${to.name}`,
            componentName: to.name, //要求路由name跟默认组件的name保持一致
            always: to.meta.keepAlive.length === 0,
            keepAlive: to.meta.keepAlive,
        })
    }
})

过程

先开始的执行执行思路如下:

线判断是否匹配,匹配就缓存组件
但是发现,keepAlive缓存的方法先于router.beforeEach执行,失败

后来换了思路

先缓存组件,然后在看是否匹配,不匹配就删除已有缓存

中间还遇到了keepAlive的坑,vue官网上没有对已经缓存后的组件操作的API,所以网上查了很多资料,发现keepAlive已经对include做了订阅,只要include变化,不在include内已缓存的组件也会被清理掉

附录

此功能还是有缺陷的
该方法需要默认路由组件name跟路由列表的name保持一致
因为keepAlive的include指令接收的值是组件name,而我们使用的是异步载入组件,所以在router.beforeEach时,组件还没有实例化,没办法拿到组件的name
当然如果是同步加载组件可以用下列代码拿到组件的name

to.matched[to.matched.length-1].components.default.name

如果有大佬知道如何解决欢迎留言讨论