Vue 全站缓存二:如何设计全站缓存
在前文Vue 全站缓存之 keep-alive : 动态移除缓存中,我们实现了
在路由离开时动态移除缓存
这一功能,以此为基础,vue 全站使用缓存成为可能。本文长篇大论啰里啰嗦巴拉巴拉,请跳着读、反复读、随机读、躺着读……
前言
从早期粗暴得将 css、js 资源设定浏览器本地缓存,到后来小图标合并成大图节省请求资源,还有动态请求304状态判断,然后 ajax 开启 web2.0 时代, pjax 大放光彩,到如今 vue.js 等前端框架的繁荣盛世,所有的这一系列发展,我认为,提速
是一个核心驱动力。
keep-alive
在 vue 里,支持 keep-alive 特性,通过 keep-alive ,不再销毁旧组件为新组件让路,而是缓存起来以待将来复用,当复用缓存组件时,如果数据没有变化甚至可以直接原样恢复。链接
keep-alive 和 vue-router
将 router-view 放置到 keep-alive 中,即可粗暴的实现所有路由页的缓存功能。
<!-- App.vue -->
<keep-alive><router-view class="transit-view"></router-view></keep-alive>
为什么要使用缓存
最常见的一个场景是新建订单时选择地址
,新建订单是一个路由页面,去选择用户现有的地址又是一个路由页面,所以理所当然的,我们希望用户选择完地址回到订单页面的时候,订单里的其他数据比如选择的优惠券啊收件日期啊都能继续保持。
在大量类似的场景里,不断的出现数据保留和复用的需求,所以 vuex 出现了,通过第三方的一个公共组件来保存和中转数据,这是一个解决方案,而且还是主流的解决方案哦。
然而换个角度,如果订单页在选择地址的时候被缓存了,回到订单页后直接复用前面的订单组件,其他数据都保留此时只要更新下地址数据,让所有的代码逻辑都集中在订单组件之中,这样的开发体验是不是会更直观更友好?
这是见仁见智的思路,各有想法,不好说谁好谁坏,我们就先继续讨论缓存组件的方案吧。
出现了一点小问题
如果所有的路由页都被缓存了,那么当你不想使用缓存的时候怎么办?比如又建了一个新订单,进入不同文章的编辑组件,比如进入不同的用户中心,缓存固然提了速,有时我们也会不想要缓存功能,特别是一些表单场景,我们既希望填写一半进入下一页面时能保留填写的数据,我们又希望新进入的表单是一个全新的表单页。
鱼和熊掌可以兼得
我们既希望填写一半进入下一页面时能保留填写的数据,我们又希望新进入的表单是一个全新的表单页。
,
换句话说,回到上一个页面时使用缓存,进入下一个页面时不使用缓存
,
再换句话说,所有页面都用缓存,只在后退(回到上一页)时移除当前页缓存,这样下一次前进(进入当前页)时因为没有缓存就自然使用全新页面
,
也就是说,只要实现后退(回到上一页)时移除当前页缓存
这个功能,就可以了。
在路由中定义位置
这是一种缓存复用的思路,为了实现后退(回到上一页)时移除当前页缓存
,因为想要实现动态确定用户的前进后退行为比较麻烦,所以,我们有个傻瓜式的方案:预测使用场景约定各路由页面的层级关系。
比如,在 routes 定义里,我们可以这么定义各路由页:
// 仅供参考,此处缺少路由组件定义
// router/index.js
routes: [
{ path: '/', redirect:'/yingshou', },
{ path: '/yingshou', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/contract_list', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/customer', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/wode', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/yingfu', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/yingfu/pact_list', meta:{rank:2.5}, },
{ path: '/yingfu/pact_detail', meta:{rank:3.5}, },
{ path: '/yingfu/expend_view', meta:{rank:4.5}, },
{ path: '/yingfu/jizhichu', meta:{rank:5.5}, },
{ path: '/yingfu/select_pact', meta:{rank:6.5}, },
{ path: '/yingfu/jiyingfu', meta:{rank:7.5}, },
]
核心的思路是,在定义路由时,在 meta 中定义一个 rank 字段来声明该路由的页面优先级, 比如 1.5 标识第 1 层如首页,2.5 表示第 2 层如商品列表页, 3.5标识第 3 层商品详情页,以此类推。
如果大家同在一层,也可以通过 1.4 和 1.5 这样小数位来约定先后层级。
总之,我们期望的是,从第1层进入第2层是前进,从第3层回到第2层是后退。
在路由跳转里动态判断移除缓存
使用Vue.mixin的方法拦截了路由离开事件,并在该拦截方法中实现后退时销毁页面缓存
。
// main.js
Vue.mixin({
beforeRouteLeave:function(to, from, next){
if (from && from.meta.rank && to.meta.rank && from.meta.rank>to.meta.rank)
{//此处判断是如果返回上一层,你可以根据自己的业务更改此处的判断逻辑,酌情决定是否摧毁本层缓存。
if (this.$vnode && this.$vnode.data.keepAlive)
{
if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache)
{
if (this.$vnode.componentOptions)
{
var key = this.$vnode.key == null
? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
: this.$vnode.key;
var cache = this.$vnode.parent.componentInstance.cache;
var keys = this.$vnode.parent.componentInstance.keys;
if (cache[key])
{
if (keys.length) {
var index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
}
delete cache[key];
}
}
}
}
this.$destroy();
}
next();
},
});
这样就行了吗
不行,这样只解决了动态移除缓存
的情况,让用户既可以进入新页面,也可以回到旧页面。前者进新页面问题不大,后者回到旧页面这件事咱还没讨论过呢。
缓存是说用就用的吗
即使是路由页面复用了缓存,也只是复用了缓存的组件和数据,在实际场景中,从列表 A 进入详情 B 再进入列表 C ,请问 列表 C 和列表 A 是同一个路由页,但他们的数据会一样吗?应该一样吗?
所以就算是缓存了也要更新数据?
看起来,我们得到了一个新结论,缓存页的数据也不可靠啊,摔,这日子快没法过了。
应该有办法的。
缓存的组件被复用时会触发 activated 事件,非缓存组件则会在创建时触发 created mounted 等一大堆事件,而同一个页面列表 A 进列表 B,因为 url 参数不同,也会触发beforeRouteUpdate事件。链接1 链接2 链接3
看起来,我们能够通过捕捉这些事件干点啥。
不对不对
第一直觉是,我们在捕捉到页面载入的事件后去拉取数据更新页面。仔细一想,我们上面罗里吧嗦废了老半天劲是为了进入缓存页面不再看那 loading 条的啊。摔,怎么又绕回来了。
笨办法
这里提供一个笨办法,事件该捕捉咱还是要捕捉,只是这是否去做拉取数据这个动作,咱可以有待商榷。
- 在所有的路由页统一注册一个方法,就叫pageenter吧,不管从啥情况进来的,咱都去触发这个方法。
// list.vue data(){ return { params : null, } }, methods:{ pageenter:function(){ this.params = { 'uuid' : this.$route.query.uuid , }; }, }
- 如果直接 watch 这个 params ,这事还是没法玩(请想下为什么?),这里可以用一个笨办法,咱将它转化成字符串再 watch,捕捉到字符串变化再去重新拉取数据,如果字符串没有变化,则啥也不做。
// list.vue computed:{ paramsToString(){ return JSON.stringify(this.params); }, }, watch:{ paramsToString:function(){ this.loadContent(); }, }, methods:{ loadContent:function(){ //此处拉取数据 //this.$http.get(....) }, }
- 在main.js里,可以用Vue.mixin拦截上文提到的三种事件,来触发 pageenter 方法。
//main.js Vue.mixin({ /*初始化组件时,触发pageenter方法*/ mounted:function(){ if (this.pageenter) { this.pageenter(); } }, // /*从其他组件返回激活当前组件时*/ activated:function(){ if (this.pageenter) { this.pageenter(); } }, /*在同一组件中,切换路由(参数变化)时*/ beforeRouteUpdate:function(to, from, next){ if (this.pageenter) { this.$nextTick(()=>{ this.pageenter(); }); } next(); }, });
后语
至此,全站缓存的框架算是基本搭好了
吗?
请继续阅读-系列篇3:Vue 全站缓存之 vue-router-then :前后页数据传递