keep-alive 的 include 属性实现 Vue 页面缓

使用 keep-alive 的 include 属性实现 Vue 页面缓

众所周知,Vue 中的 keep-alive 可以对组件进行缓存,搭配上 vue-router 的 <router-view> 则可以实现页面缓存。

但网上大多数的方案都是采用在 router 的 meta 属性里增加一个 keepAlive 字段,然后在父组件或者根组件中,根据 keepAlive 字段的状态使用 <keep-alive> 标签,实现对 <router-view> 的缓存,如下:

如果要对页面动态控制是否需要缓存,则是在 beforeRouteLeave() 里去控制 keepAlive 的状态。

这个方法看似简单,但问题挺多,网上的解决方案似乎也不太理想,我甚至连尝试都懒得去尝试。

因为这个方案为了解决一个问题,反而创造出了一堆问题,为了解决这一堆问题,又引入了各种“奇思妙想”、“剑走偏锋”的骚操作,光是看大家的代码就让我头大。

在思考并搜索还有什么更好解决方案的时候,我无意翻看到 Vue 的官方文档,在 keep-alive 的介绍里看到, 2.1.0 里新增了 include 和 exclude 这两个属性,这似乎给我了一点思路。

于是带着这两个关键词,重新去百度里搜寻了一番,果然,已经有现成的解决方案了。

实现思路

这个解决方案思路其实很清晰,因为 include 属性支持传入字符串、正则和数组,利用 vuex 全局去管理 include 里的数据,就可以达到动态管理缓存。

比起开篇介绍的那个方案,这个方案从始至终都没有销毁 <router-view> ,从而规避了很多无形的坑。加上 include 本身又是官方提供的属性,跟着官方走,准没错!

实现代码

老罗说的好:少废话,先看东西。

首先 include 属性里存放的是组件的 name ,也就是说,我们的页面组件必须都先设置上 name ,注意了,这个 name 并不是 router 里的 name ,而是组件的 name 。

接着,因为 include 的数据是通过 vuex 动态管理的,所以需要定义一个 store ,代码如下:

conststate = {

    list: []

}

constmutations = {

add(state, name){

state.list.indexOf(name) <0&& state.list.push(name)

    },

remove(state, name){

state.list = state.list.filter(v=>{

returnv != name

        })

    },

clean(state){

        state.list = []

    }

}

exportdefault{

namespaced:true,

    state,

    mutations

}

在 mutations 里定义了三个对 list 状态更改的事件,分别是 add 、remove 、clean ,随后我们在父组件或者根组件中就可以这样使用了。


准备工作做好后,那什么时候去控制 include 里的数据呢?那就是在页面进入和离开的时候去控制就行,这里就需要用到 beforeRouteEnter() 和 beforeRouteLeave() 这两个钩子函数。

我们假设这样一个场景,有这样两个页面,一个商品列表页(A),一个商品详情页(B),当从 A 页面跳转到 B 页面的时候,希望把 A 页面缓存上,这样在 B 页面做 $router.go(-1) 这种返回操作的时候,可以继续浏览 A 页面的内容。代码如下:


// A 页面

// 页面进入前

beforeRouteEnter(to,from, next){

next(vm=>{

vm.$store.commit('keepAlive/add','List')

    })

},

// 页面离开前

beforeRouteLeave(to,from, next){

if(['detail'].indexOf(to.name) <0) {

this.$store.commit('keepAlive/remove','List')

    }

    next()

}

这里有一点需要注意,当离开 A 页面前,需要判断去往的页面是否为 B 页面,也就是这句 if (['detail'].indexOf(to.name) < 0) 代码(这里的 detail 是去往页面 router 里的 name ,并非组件的 name),如果去往的页面不是 B 页面,则清除缓存,比如从 A 页面返回了更上级的页面,如果不清除,下次再进来的时候,会直接调取缓存,而不是全新打开。

是不是很简单?思路是不是也特别清晰?先别着急,我们来踩踩坑。

踩坑

缓存无法清除

以上面举的例子,想要清除 A 页面的缓存,必须从 A 页面进行操作,比如从 A 页面返回到更上级的 C 页面。

但在实际业务中,页面之间的联系并非是一条直线的。比如从 A 页面进入 B 页面, B 页面有个功能按钮是可以直接进入 C 页面的,这时候再从 C 页面进入 A 页面, A 页面的缓存是还存在的,导致打开还是上次缓存的内容,而不是全新的 A 页面。

这时候就需要用到 $store.commit('keepAlive/clean') 了,因为涉及到具体业务逻辑,所以在什么时候调用 clean 方法需要具体页面具体分析。我的原则就是在顶级,或者次顶级页面上,做缓存清空处理,比如例子中的 C 页面,或者是一般项目的首页。

页面刷新后缓存失效

关于 Vue 刷新的问题,我在《Vue中刷新当前页的几种方式及优劣分析》已经有提到过。

其中方案三的刷新,无法和 keep-alive 共存,所以在需要缓存的相关页面里,建议使用方案二,或者使用方案四,手动进行数据更新。

如何更新缓存

有这么一种情况,从 A 页面进入 B 页面,在 B 页面做了一些操作后,返回 A 页面,这时候 A 页面部分数据要进行更新。

最常见的就是订单列表页,从订单列表页进入订单详情页,在订单详情页里做了一些操作,比如关闭该订单,这时改变了订单的状态,当返回的时候,订单列表页虽然被缓存了,但列表里的信息要进行更新。

我自己想到的方案是,在 B 页面离开前,往去往页面的 meta 里添加一个特定字段,例如 to.meta.returnRefresh ,至于这个字段什么时候要添加,我们可以自己控制。然后在订单列表页的 activated() 钩子里处理即可。


// 订单详情页

beforeRouteLeave(to,from, next){

if(['orderList'].indexOf(to.name) >=0&&this.dataChange) {

to.meta.returnRefresh =true

    }

    next()

}

// 订单列表页

activated(){

if(this.$route.meta.returnRefresh) {

// 业务代码

    }

}

我的这个方案没什么大问题,就是在体验上有点欠缺。因为 A 页面的更新,是当 A 页面被激活后才会进行,能明显看到返回 A 页面后,数据才进行更新,整个过程用户是有感的。

于是我开始在网上搜寻相关解决方案,同时在用 Vue 开发者工具操作的时候发现一个细节:

因为 A 页面被缓存了,所以实际上 A 页面和 B 页面这两个 <router-view> 是并存的,只是其中一个被隐藏了。既然这两个组件是并存的,我开始有方向了,搜索一圈之后,找到了解决方案。

简单来说,就是兄弟组件之间的通信,父子组件的通信我们比较了解,但兄弟平级组件之间的通信,和父子组件不一样,他们需要借助事件总线,因为 $on() 和 $emit() 的事件必须是在一个公共的实例上才能触发,那我们可以新建一个 Vue 实例当作事件总线,达到可以不管组件之间的父子关系,都能通过这个实例通信的目的。

这里我偷懒了,直接把现有 Vue 实例当做事件总线,并将它绑定到 Vue 原型链上,方便后续

// main.js

Vue.prototype.$eventBus =newVue({

    router,

    store,

render:h=>h(App)

}).$mount('#app')

准备好后,我们来看下如何在订单详情页通知订单列表页进行数据更新。


// 订单详情页

this.$eventBus.$emit('refreshOrderList')

// 订单列表页

mounted(){

this.$eventBus.$on('refreshOrderList',() =>{

// 业务代码

    })

},

beforeDestroy(){

this.$eventBus.$off('refreshOrderList')

}

我们在订单详情页里任何时候都可以通过 this.$eventBus.$emit('refreshOrderList') 去通知订单列表页更新数据,这样数据的更新对用户来说是无感的,用户返回订单列表页的时候,数据是已经更新好了,对用户体验上有明显的提升。

避免意外情况,在订单列表页被销毁前,手动销毁下监听的事件,这样就万无一失了。

总结

其实通篇的解决方案,网上都能找到类似的影子,如何将它们合理的使用在项目或产品中,这才是我们需要多去思考的。

其次我似乎没有遇到从 include 列表移除组件,组件没有被销毁的问题,可能 Vue 已经修复了这个 bug 吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容