Vue 全站缓存二:如何设计全站缓存

10,574 阅读5分钟

在前文Vue 全站缓存之 keep-alive : 动态移除缓存中,我们实现了在路由离开时动态移除缓存这一功能,以此为基础,vue 全站使用缓存成为可能。

本文长篇大论啰里啰嗦巴拉巴拉,请跳着读、反复读、随机读、躺着读……

系列篇1:Vue 全站缓存之 keep-alive : 动态移除缓存

本篇为系列篇2:Vue 全站缓存二:如何设计全站缓存

系列篇3:Vue 全站缓存之 vue-router-then :前后页数据传递

系列篇4:Vue 全站缓存之 vue-router-then :实现原理

前言

从早期粗暴得将 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 :前后页数据传递

原文来自阿星的博客:wanyaxing.com/blog/201807…