Vue 爬坑之旅 -- keepAlive 与 vue-router 结合使用实现页面缓存及记住滚动位置功能的实现及几个需要注意的点

平时业务开发中,列表页面是很常见的,而列表页面有一个很常见的需求就是要缓存整个页面,而且还要记住列表的滚动位置,当点击某个列表项进入详情页,再返回到列表页面的时候,要能够回到原来的滚动位置。

上面的需求我相信业务开发人员基本都碰到过,那么在 vue 中,这个需求要怎么实现呢,这票文章就来说一下,在实现这个功能的过程中,还有几个需要注意的点后面也会说到。

一,用 keep-alive 实现路由缓存

在 vue 项目中,要想实现缓存功能,有一个很简单方便的方法就是使用 keep-alive 组件,keep-alive 可以实现组件或路由级的缓存,这里就以路由缓存为例来说下。

1,在 App.vue 中的路由组件 router-view 上加上 keep-alive

像页面缓存这种功能,在一个应用中可能会有的页面不需要,而有的页面又需要,所以就需要将这功能放在全局,并做成可配置的,这样才能进行灵活配置。

而整个项目的所有页面其实都是挂在 app 这个节点下,通过 router-view 加载和显示出来的。所以我们在 app.vue 这个文件这里给 router-view 加上 keep-alive。

<div id="app" class="main-app">
    <!--缓存想要缓存的页面,实现后退不刷新-->
    <!--加上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 组件来做缓存,所以生命周期等钩子函数的执行顺序就有了两种情况。

  1. 不使用 keep-alive 时
    beforeRouteEnter --> created --> mounted --> destroyed
  2. 使用 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
    },

这里面的判断逻辑有点小绕,需要好好理解下。完成上面的代码也就可以实现从详情页返回不刷新,自动滚动到原先的位置,而从其他页面进入则重新获取数据的需求。

四,额外需要注意的地方

1,keep-alive 与 lazyload 同时使用时要注意

这个问题是我偶尔发现的,有时候会发现使用了 lazyload 加载的图片有时会显示不正确,刷新下就又正常了。这其实是 lazyload 导致的问题,当单独操作一二个数据添加或删除的时候,lazyload 会导致图片的显示位置错乱,我还没找到很好的处理办法,只有先暂时去掉 lazyload。如果有更好的处理方式,烦请告知。

2,要注意页面布局,不要限制页面滚动位置

在使用了 keep-alive 做路由级缓存的页面,不要限定页面的滚动位置,让页面能够自然滚动就行,不要给滚动区域设置固定高度。设置了固定高度的话,记录滚动位置的功能就会不起作用,缓存功能是还在,但是并不会自动记住滚动位置。

以上就是这次实现这个功能的过程总结,参考文章如下:
keep-alive 组件缓存问题
vue缓存页面返回到指定滚动位置
vue-router在返回时返回到上次滚动位置
移动端基于vue-router的滚动位置记录(keep-alive)
另辟蹊径:vue单页面,多路由,前进刷新,后退不刷新

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值