vue如何使用router.meta.keepAlive对页面进行缓存
作者:doit_damao
使用router.meta.keepAlive对页面进行缓存
需求:
1. 从stockList页面到stockInfo页面,从stockInfo页面返回stockList,缓存stockList页面
2. 从其他页面进入到stockList页面,则不缓存stockList页面
路由配置文件中:
{ path: '/stockSelectionAndDiagnosticStocks', name: 'stockSelectionAndDiagnosticStocks', component: stockSelectionAndDiagnosticStocks, redirect: '/stockSelectionAndDiagnosticStocks/stockList', children: [ { path: '/stockSelectionAndDiagnosticStocks/stockList', name: 'stockList', component: stockList, // 此处设置的原因:保证第一次从stockList进入到stockInfo中是缓存了的 meta: { keepAlive: true } }, { path: '/stockSelectionAndDiagnosticStocks/stockInfo', name: 'stockInfo', component: stockInfo } ] }
App.vue中:
<div id="app"> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> </div>
stockList.vue中:
beforeRouteLeave (to, from, next) { // 从列表页去到别的页面,如果不是详情页,则不缓存列表页 if (to.name !== 'stockInfo') { this.$route.meta.keepAlive = false } else { this.$route.meta.keepAlive = true } next() }
*** 增加需求:从stockInfo页面通过“特殊的”点击事件回到stockList,stockList页面的列表需要按照字段更新 *** stockInfo.vue中:(其中setStore、getStore是封装的sessionStorage)
clickHistoryItem (val) { setStore('searchName', val) this.$router.push({ name: 'stockList' }) }
stockList.vue中:
// 只有当缓存的情况写才触发这两个钩子函数 activated () { if (getStore('searchName')) { this.pageIndex = 1 this.searchStockFirstStep(getStore('searchName')) } }, deactivated () { removeStore('searchName') }
使用keepAlive缓存页面及记忆上次浏览位置,并刷新页面
平时业务开发中,列表页面是很常见的,而列表页面有一个很常见的需求就是要缓存整个页面,而且还要记住列表的滚动位置,当点击某个列表项进入详情页,再返回到列表页面的时候,要能够回到原来的滚动位置。
上面的需求我相信业务开发人员基本都碰到过,那么在 vue 中,这个需求要怎么实现呢,这票文章就来说一下,在实现这个功能的过程中,还有几个需要注意的点后面也会说到。
一、用keep-alive实现路由缓存
在 vue 项目中,要想实现缓存功能,有一个很简单方便的方法就是使用 keep-alive 组件,keep-alive 可以实现组件或路由级的缓存,这里就以路由缓存为例来说下。
1,在 App.vue 中的路由组件 router-view 上加上 keep-alive
像页面缓存这种功能,在一个应用中可能会有的页面不需要,而有的页面又需要,所以就需要将这功能放在全局,并做成可配置的,这样才能进行灵活配置。
而整个项目的所有页面其实都是挂在 app 这个节点下,通过 router-view 加载和显示出来的。所以我们在 app.vue 这个文件这里给 router-view 加上 keep-alive。
<!--缓存想要缓存的页面,实现后退不刷新--> <!--加上v-if的判断,可以自定义想要缓存的组件,自定义在router里面--> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> </div>
- 注意点一:用 keep-alive 包裹需要缓存的路由,但是判断条件不要写在 keep-alive 上,需要写在 router-view 上,不然会没有作用
- 注意点二:当 router-view 有 transition 过渡动画时的写法。
如果需要自定义路由的切换过渡动画,那么就需要在 router-view 上加上 transition 组件,当加上 transition 之后,HTML 层级就达到了三级,然后 transition ,keep-alive ,router-view ,v-if 这几者之间可以有多种不同的组合,在尝试了各种不同的组合之后,我发现只有一种写法可以达到缓存效果,但也有一点小瑕疵,那就是缓存的页面不会触发过渡动画,这也是一个比较遗憾的地方。代码如下:
<div id="app" class="main-app"> <!--缓存想要缓存的页面,实现后退不刷新--> <!--加上v-if的判断,可以自定义想要缓存的组件,自定义在router里面--> <!--keepAlive 的路由不会触发 transition,参见 https://github.com/vuejs/vue-router/issues/811 --> <transition name='fade' mode="out-in"> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> </transition> <transition name='fade' mode="out-in"> <router-view v-if="!$route.meta.keepAlive"></router-view> </transition> </div>
2,在 router.js 中为需要缓存的路由设置缓存参数
在上面的代码中,有一个判断条件 $route.meta.keepAlive ,这个条件就是用来区分该路由是否需要缓存的,这个条件需要写在 router.js 路由表文件中。
{path: ‘/msg', name: ‘Message', meta: {keepAlive: true}, component: Message},
如上,就是在需要缓存的路由里面,给该路由加上 meta 元信息,在里面设置是否需要缓存即可。
二、记录页面滚动位置
要记住滚动位置,有多种实现方式,然而最简单,最便捷的方式就是在路由表中加上 scrollBehavior,这也是官方推荐的方式。
new Router({ mode: 'history', scrollBehavior (to, from, savedPosition) { if (savedPosition) { //如果savedPosition存在,滚动条会自动跳到记录的值的地方 return savedPosition } else { return {x: 0, y: 0}//savedPosition也是一个记录x轴和y轴位置的对象 } }, routes: [] })
只要在 router.js 中加入如上的 scrollBehavior 那段代码,就可以实现 router-view 的记录滚动位置的功能了,这功能是 vue 内封装的,用起来简单方便。
做完上面两步就可以实现简单的页面缓存和记住滚动位置的功能了,但是真正用起来却发现仅仅是这样还是不够的。按照上面的代码缓存功能是实现了,但是你会发现,被缓存的页面不会再重新请求数据了(没有做下拉刷新等操作的时候),这样其实是不太好的,其实我们大多数时候需要的是从列表详情页回到列表的时候不刷新,但是从其他页面进入列表页的时候需要重新请求数据。那么要怎么实现这么个功能呢?
三、详情页返回列表页不刷新,其他页面进入列表页刷新
在前面的代码中,我们实现了路由级的页面缓存,也记住了页面滚动位置,但同时也发现被缓存的页面不会再重新请求数据,这样也是不行的,我们现在就来解决这个问题。
要解决这个问题,首先需要用到 vue 的生命周期和路由钩子的相关知识点。因为我们这里使用了 keep-alive 组件来做缓存,所以生命周期等钩子函数的执行顺序就有了两种情况。
不使用 keep-alive 时
beforeRouteEnter --> created --> mounted --> destroyed
使用 keep-alive 时
beforeRouteEnter --> created --> mounted --> activated --> deactivated
再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated 。created和mounted不会再执行。
基于上面的两点,我们就可以解决被缓存的页面一直不刷新的问题了。
1,在路由的 mate 中添加一个变量,表示是否需要刷新
{path: ‘/msg', name: ‘Message', meta: {keepAlive: true, isNeedRefresh: true}, component: Message},
2,在页面组件的 data 中定义一个变量,表示是否是第一次进入该页面
data () { return { isFirstEnter: false, } },
3,在相关的钩子函数中判断是否是第一次进入页面,是否需要重新加载数据
beforeRouteEnter (to, from, next) { // 利用路由元信息中的 meta 字段设置变量,方便在各个位置获取。这就是为什么在 meta 中定义 isNeedRefresh。 // 当从详情页返回时,将 isNeedRefresh 设为 false,表示不刷新数据 if (from.name === 'MsgDetail') { to.meta.isNeedRefresh = false } next() }, mounted () { // 只有第一次进入或者刷新页面后才会执行此钩子函数,使用keep-alive后(2+次)进入不会再执行此钩子函数 // isFirstEnter 用来标记是否第一次进入该页面,以防止用户在详情页手动刷新页面,回到该页面后不再请求数据 this.isFirstEnter = true }, activated () { if (this.$route.meta.isNeedRefresh || this.isFirstEnter) { // 如果 isNeedRefresh 是 true,表明需要获取新数据,否则就不再请求,直接使用缓存的数据 this.queryListData() } // 恢复成默认的 false,避免 isFirstEnter 一直是 true,导致重复获取数据 this.isFirstEnter = false // 恢复成默认的 true,避免 isNeedRefresh 一直是 false,导致下次无法获取数据 this.$route.meta.isNeedRefresh = true },
这里面的判断逻辑有点小绕,需要好好理解下。
完成上面的代码也就可以实现从详情页返回不刷新,自动滚动到原先的位置,而从其他页面进入则重新获取数据的需求。
四、返回记录位置的页面需要进行刷新数据
凡是被keep-alive包裹住的页面都会被缓存,如果想刷新内容要启用activated函数,用法同created,activated只有在被keep-alive包裹时会触发,activated函数一进入页面就触发
activated(){ //返回页面时刷新数据,需要重新请求的写在这里 this.getParkList(); },
五、额外需要注意的地方(没测)
1,keep-alive 与 lazyload 同时使用时要注意
这个问题是我偶尔发现的,有时候会发现使用了 lazyload 加载的图片有时会显示不正确,刷新下就又正常了。这其实是 lazyload 导致的问题,当单独操作一二个数据添加或删除的时候,lazyload 会导致图片的显示位置错乱,我还没找到很好的处理办法,只有先暂时去掉 lazyload。如果有更好的处理方式,烦请告知。
2,要注意页面布局,不要限制页面滚动位置
在使用了 keep-alive 做路由级缓存的页面,不要限定页面的滚动位置,让页面能够自然滚动就行,不要给滚动区域设置固定高度。设置了固定高度的话,记录滚动位置的功能就会不起作用,缓存功能是还在,但是并不会自动记住滚动位置。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。