vue-router实战缓存处理看这篇准没错,包括处理keepAlive失效情况。

6,650 阅读5分钟

这周在开发过程中发现了需要利用vue-router缓存处理,具体场景就是我在A页面填写表单中,需要跳转到另外的页面B,在B页面中点击返回,A页面中的数据不会丢失。

第一次实现

我一想,这不是很简单的事情吗(当然也是因为以前也做过),vue-router不是有自带导航守卫,我直接在A页面中利用导航守卫进行判断,在加上利用vue-router中的keep-alive,以及router-view整整不就完事儿了吗。于是就很愉快的写出下列代码:

    // A页面中
  beforeRouteLeave(to, from, next) {
    from.meta.keepAlive = to.name === 'xxxx'; // xxxx代表B页面
    next();
  },
 
 // App.vue页面
  <keep-alive>
    <router-view v-if='$route.meta.keepAlive' />
  </keep-alive>
  <router-view v-if='!$route.meta.keepAlive' />

第一次自测

代码是敲完了,自己本地总得先测试下吧,不然上到测试环境,给测试怼就不好了(测试还不是个妹子)。结果一侧,眉头一皱,发现事情不对劲,大概描述下步骤

  1. 我从C页面进入A页面,写一点东西,然后退出来再进入A页面,表现符合预期(A页面的数据都被清空了)。通过
  2. 我在刚刚的基础上(没刷新)进入A页面写一点东西,进入B页面再返回A页面,这时候数据存在,也符合预期。通过

以为圆满的完成了任务,本准备接下来的一连串的git操作加提测。然而根据多次经验,F5刷新下出现的结果会和刚刚操作的结果不一样。

出现bug

于是我就在A页面进行了刷新(为啥要在A页面进行呢?只是因为我当时正好在A页面而已)。然后把上面的1和2的步骤颠倒了一下,结果问题就出来了。

  • 写了数据在A页面,进入B页面,返回A页面,what我的数据呢,怎么不见了?怎么都被清空了,我想要的应该是数据还在的结果啊。

刚刚未刷新之前我们的测试结果是通过的,但是这一次却不通过,说明我们在导航守卫中的代码是没有问题的(不然第一次都不会对)。
于是上网查阅一翻,发现这个问题确实是存在的,第一次进入了B页面再回来确实是不会将数据保存住,而且在断点调试过程中,keepAlive确实是被赋值为true了!这可真的奇了怪了。

第二次实现

这时候反复去看自己写的代码,无论是App.vueroute.js还是A页面中的代码。还是没啥思路,只不过发现在route.js中我将A页面的route写成了:

 // A页面路由配置
  path: 'xxxx',
  name: 'APage',
  component: AComponent,
  meta: {
    keepAlive: false
  }

然后在想,第一次没保存是不是因为默认给了false的原因,于是我就将上面meta对象修改了一下:

  meta: {
    keepAlive: true
  }

第二次自测

根据上述两次测试再进行了一次,结果都通过了,很开心,这下终于可以提测了,打开控制台就是一顿输出,整体花的时间也不是特别长,还可以多看会别的(美滋滋)。

最后次实现

以为这方案完美无暇的时候,隔天一大早上班就被测试说,你这跳转缓存数据有问题的,我当时就想脱口而出:不可能、你本地清下缓存、你环境有问题。

上次实现遗留bug

结果测试当着我面进行了一次操作,发现第一次走是没问题的,第二次在进行相应的操作的时候就发现A页面的数据有问题,总的流程:

  1. 首先在C页面进入A页面,在A页面写入数据返回C页面,进入A页面,数据清除。通过
  2. (即使刷新)再进入A页面写入数据,进入B页面再返回A页面,数据仍旧存在。通过
  3. 这时候在A页面进行写入数据,再进入B页面,结果数据就变成了第二个步骤中的数据,刚刚新写入的数据不见了。GG

这时候发现投机取巧的改变keepAlive本质上并没有作用,bug依旧是存在的,只不过方式不同了。

最后次实现

去看了看vue-router的官网,想起了还有一种实现缓存的方式,include以及exclude。于是就用了这套方案看能不能完美解决所有问题:

<!--
 // App.vue
 // keepAliveList是所有想要被缓存组件的name数组。
 /
-->
<keep-alive :include="aliveComponent">
  <router-view />
</keep-alive>
// App.vue
import { mapState } from 'vuex'
export default{
  data() {
    return {}
  },
  ...mapState({
    aliveComponent: store => store.aliveComponent
  })
}
// A.vue
// 最重要一点就是别忘了要写组件名
export default {
  name: 'AComponent',
  data() {
      return {}
  },
  beforeRouteLeave(to, from, next) {
    const status = to.path === 'xxxx'; // xxxx表示B页面
    this.$store.commit(updateAliveComponent', { name: 'AComponent', status: status });
    next();
  },
},

结果发现这样写还是行不通,因为利用了vuex的关系发现执行完next(),keepAliveList不一定就马上就得到了相对应结果。
于是就想了两种度过keepAliveList数据更新时间的办法:

// nextTick
// 结果表明失败了
this.$nextTick(() => {
  next();
});

// setTimeout
// 为什么用20呢 因为我发现没有值的时候不一定百分比可以,于是加了点时间。
setTimeout(() => {
  next();
}, 20);

最终代码

测试了好几次没啥问题,为了防止以后还会有类似需求做下优化,整体代码如下:

<!--
 // App.vue
 // keepAliveList是所有想要被缓存组件的name数组。
-->
<keep-alive :include="keepAliveList">
  <router-view />
</keep-alive>
// App.vue
import { mapState } from 'vuex'
export default{
  data() {
    return {}
  },
  ...mapState({
    keepAliveList: _ => _.keepAliveList
  })
}
// A.vue
// 最重要一点就是别忘了要写组件名
export default {
  name: 'AComponent',
  data() {
      return {}
  },
  beforeRouteLeave(to, from, next) {
    const status = to.path === 'xxxx'; // xxxx表示B页面
    this.$store.commit('updateAliveList', { name: 'createRule', status: status });

    setTimeout(() => {
      next();
    }, 20)
  },
},

// vuex.js
  state: {
    keepAliveList: [],
  },
  mutations: {
    updateAliveList(state, { name, status }) {
      if (status) {
        state.keepAliveList.push(name);
      } else {
        const index = state.keepAliveList.indexOf(name);
        index >= 0 && state.keepAliveList.splice(index, 1);
      }
    }
  },

总结

利用include实现最大的不足点就是每一个需要缓存的组件都必须要写name值,很麻烦。同时我在导航守卫中加入了20ms定时器这个不稳定的因素,这不利于理解以及扩展。但是在目前来说,是完整的一套有效的缓存方案了,有更多方案的朋友可以留言讨论。

github地址,陆续会有更多实战博客。