这周在开发过程中发现了需要利用
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' />
第一次自测
代码是敲完了,自己本地总得先测试下吧,不然上到测试环境,给测试怼就不好了(测试还不是个妹子)。结果一侧,眉头一皱,发现事情不对劲,大概描述下步骤
- 我从C页面进入A页面,写一点东西,然后退出来再进入A页面,表现符合预期(A页面的数据都被清空了)。通过
- 我在刚刚的基础上(没刷新)进入A页面写一点东西,进入B页面再返回A页面,这时候数据存在,也符合预期。通过
以为圆满的完成了任务,本准备接下来的一连串的git操作加提测。然而根据多次经验,F5刷新下出现的结果会和刚刚操作的结果不一样。
出现bug
于是我就在A页面进行了刷新(为啥要在A页面进行呢?只是因为我当时正好在A页面而已)。然后把上面的1和2的步骤颠倒了一下,结果问题就出来了。
- 写了数据在A页面,进入B页面,返回A页面,what我的数据呢,怎么不见了?怎么都被清空了,我想要的应该是数据还在的结果啊。
刚刚未刷新之前我们的测试结果是通过的,但是这一次却不通过,说明我们在导航守卫中的代码是没有问题的(不然第一次都不会对)。
于是上网查阅一翻,发现这个问题确实是存在的,第一次进入了B页面再回来确实是不会将数据保存住,而且在断点调试过程中,keepAlive确实是被赋值为true了!这可真的奇了怪了。
第二次实现
这时候反复去看自己写的代码,无论是App.vue、route.js还是A页面中的代码。还是没啥思路,只不过发现在route.js中我将A页面的route写成了:
// A页面路由配置
path: 'xxxx',
name: 'APage',
component: AComponent,
meta: {
keepAlive: false
}
然后在想,第一次没保存是不是因为默认给了false的原因,于是我就将上面meta对象修改了一下:
meta: {
keepAlive: true
}
第二次自测
根据上述两次测试再进行了一次,结果都通过了,很开心,这下终于可以提测了,打开控制台就是一顿输出,整体花的时间也不是特别长,还可以多看会别的(美滋滋)。
最后次实现
以为这方案完美无暇的时候,隔天一大早上班就被测试说,你这跳转缓存数据有问题的,我当时就想脱口而出:不可能、你本地清下缓存、你环境有问题。
上次实现遗留bug
结果测试当着我面进行了一次操作,发现第一次走是没问题的,第二次在进行相应的操作的时候就发现A页面的数据有问题,总的流程:
- 首先在C页面进入A页面,在A页面写入数据返回C页面,进入A页面,数据清除。通过
- (即使刷新)再进入A页面写入数据,进入B页面再返回A页面,数据仍旧存在。通过
- 这时候在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地址,陆续会有更多实战博客。