vue-router | keep-alive (切换不刷新,保持滚动位置)

6,974 阅读4分钟

切换底部导航栏

预期效果

项目需要实现 【home首页】页面(已经浏览了部分内容) —— >切换到【my我的】页面(查看设置信息) ———> 切换回【home首页】,实现【home首页】不刷新,并保留页面浏览滚动的位置

router切换.jpg

页面整体架构

截屏2021-08-28 上午10.37.08.png

src/router/index.js

const routes = [
  { path: '/login', name: 'login', component: () => import('@/views/login') },
  {
    path: '/',
    // name: 'layout',如果有默认子路由,则此处的name没有意义
    component: () => import('@/views/layout'),
    children: [
      {
        path: '', // 默认子路由,只能有一个
        name: 'home',
        component: () => import('@/views/home')
      },
      ...省略其他代码
      {
        path: 'my',
        name: 'my',
        component: () => import('@/views/my')
      }
    ]
  }
]

src/App.vue

<template>
  <div id="app">
      <router-view>
      </router-view>
  </div>
</template>

src/layout/index.vue

<template>
  <div class="layout-container">
      <!-- 子路由 -->
      <router-view>
      </router-view>
   

    <!-- 标签导航栏 -->
    <!--route: 开启路由模式-->
    <van-tabbar class="layout-tabbar" route>
      <van-tabbar-item to="/">
        <i slot="icon" class="toutiao toutiao-shouye"></i>
        <span class="text">首页</span>
      </van-tabbar-item>
      
      ...省略其他代码
      
      <van-tabbar-item to="/my">
        <i slot="icon" class="toutiao toutiao-wode"></i>
        <span class="text">我的</span>
      </van-tabbar-item>
    </van-tabbar>
  </div>
</template>

src/home/index.vue---首页组件

<template>
  <div class="home-container">
    <!-- 导航栏 ---------------------------------------->
    <van-nav-bar />

    <!-- 频道列表 ---------------------------------------->
    <!--
    <van-tabs v-model="active" animated swipeable>
      <van-tab :key="item.id" v-for="item in channels" :title="item.name">
        <!-- 频道的文章列表-----article-list列表子组件-------------------------- -->
        <article-list :channel="item"></article-list>
      </van-tab>

      <!-- 占位元素 -->
      <div slot="nav-right" class="placeholder"></div>
      <div slot="nav-right" class="hamburger-btn">
        <i class="toutiao toutiao-gengduo"></i>
      </div>
    </van-tabs>
  </div>
</template>

src/home/components/article-list.vue

<template>
  <div class="article-list" ref="articleListRef">
    ...省略代码
  </div>
</template>

keep-alive知识补充

什么是KeepAlive

首先,我们要明确我们谈的是TCP的 KeepAlive 还是HTTP的 Keep-Alive。TCP的KeepAlive和HTTP的Keep-Alive是完全不同的概念,不能混为一谈。实际上HTTP的KeepAlive写法是Keep-Alive,跟TCP的KeepAlive写法上也有不同。

  • TCP的keepalive是侧重在保持客户端和服务端的连接,一方会不定期发送心跳包给另一方,当一方端掉的时候,没有断掉的定时发送几次心跳包,如果间隔发送几次,对方都返回的是RST,而不是ACK,那么就释放当前链接。设想一下,如果tcp层没有keepalive的机制,一旦一方断开连接却没有发送FIN给另外一方的话,那么另外一方会一直以为这个连接还是存活的,几天,几月。那么这对服务器资源的影响是很大的。

  • HTTP的keep-alive一般我们都会带上中间的横杠,普通的http连接是客户端连接上服务端,然后结束请求后,由客户端或者服务端进行http连接的关闭。下次再发送请求的时候,客户端再发起一个连接,传送数据,关闭连接。这么个流程反复。但是一旦客户端发送connection:keep-alive头给服务端,且服务端也接受这个keep-alive的话,两边对上暗号,这个连接就可以复用了,一个http处理完之后,另外一个http数据直接从这个连接走了。减少新建和断开TCP连接的消耗。

二者的作用简单来说:

HTTP协议的Keep-Alive意图在于短时间内连接复用,希望可以短时间内在同一个连接上进行多次请求/响应。

TCP的KeepAlive机制意图在于保活、心跳,检测连接错误。当一个TCP连接两端长时间没有数据传输时(通常默认配置是2小时),发送keepalive探针,探测链接是否存活。

总之,记住HTTP的Keep-Alive和TCP的KeepAlive不是一回事。

tcp的keepalive是在ESTABLISH状态的时候,双方如何检测连接的可用行。而http的keep-alive说的是如何避免进行重复的TCP三次握手和四次挥手的环节。

Vue中的KeepAlive

vue可以通过<keep-alive>元素包裹组件,实现缓存,下次使用时不需要重新创建该组件。

但存在一个问题:keep-alive包裹的组件中有滚动元素时,keep-alive不会储存滚动位置。实现切换(后退)不刷新主要依据keep-alive组件的activateddeactivated这两个生命周期钩子函数。

vue钩子函数的执行顺序:

  • 不使用keep-alive

    • beforeRouteEnter --> created --> mounted --> destroyed
  • 使用keep-alive

    • 初次进入页面,beforeRouteEnter --> created --> mounted --> activated --> deactivated

    • 再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated。created和mounted不会再执行。

      • activated在keep-alive组件激活时调用
      • deactivated在keep-alive组件被停用时调用.

项目实际应用

在该项目中,首先需要缓存layout路由的数据,不需要缓存login路由的数据

其次,进入layout路由后,只需要记录首页my路由下缓存的数据,其他页面(如my)的数据不需要缓存

  1. 为需要缓存的页面,添加meta: { keepAlive: true }属性

src/router/index.js

const routes = [
  { path: '/login', name: 'login', component: () => import('@/views/login') },
  {
    path: '/',
    component: () => import('@/views/layout'),
*    meta: { keepAlive: true },// 需要被缓存
    children: [
      {
        path: '', 
        name: 'home',
*        meta: { keepAlive: true },// 需要被缓存
        component: () => import('@/views/home')
      },
      ...省略其他代码
  1. 使用keep-alive缓存对应的组件 5251630070446_.pic_hd.jpg App.vue
<template>
  <div id="app">
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive">
        <!-- 这里是会被缓存的视图组件,比如 Layout! -->
      </router-view>
    </keep-alive>

    <router-view v-if="!$route.meta.keepAlive">
      <!-- 这里是不被缓存的视图组件,比如 Login! -->
    </router-view>
  </div>
</template>

layout.vue

<template>
  <div class="layout-container">
    <keep-alive>
      <!-- 子路由 -->
      <router-view v-if="$route.meta.keepAlive">
        <!-- 这里是会被缓存的视图组件,比如 Home! -->
      </router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive">
      <!-- 这里是不被缓存的视图组件,比如 qa! -->
    </router-view>

    <!-- 标签导航栏 -->
    ...省略其他代码

或者通过name来找到对应的组件

# 缺点:需要知道组件的 name,项目复杂的时候不是很好的选择
<keep-alive include="a">
  <component>
    <!-- name 为 a 的组件将被缓存! -->
  </component>
</keep-alive>可以保留它的状态或避免重新渲染

<keep-alive exclude="a">
  <component>
    <!-- 除了 name 为 a 的组件都将被缓存! -->
  </component>
</keep-alive>可以保留它的状态或避免重新渲染
  1. 通过记录scollTop,来保持滚动位置(滚动的是home组件里article-list组件的区域) src/home/components/article-list.vue
<template>
  <div class="article-list" ref="articleListRef">
    ...省略代码
  </div>
</template>

<script>
import { getArticles } from '@/api/article.js'
// 导入 文章列表项组件
import ArcticleItem from '@/components/article-item'
export default {
  data() {
    return {
      ....
      scrollTop: 0 // 记录页面滑动的位置
      ....
   }
  },
  activated() {
    // 该路由组件激活时,记录滚动位置
    this.$refs.articleListRef.scrollTop = this.scrollTop
  },
  deactivated() {},
  mounted() {
    // this指向组件的实例,$el指向当前组件的DOM元素 
    // this.$el 等价于 this.$refs.articleListRef
    
    // 拿到 当前滑动list的dom元素
    const artListDom = this.$refs.articleListRef
    // 当其滚动时,记录滚动后的top位置
    artListDom.addEventListener('scroll', () => {
      this.scrollTop = artListDom.scrollTop
    })
  },

参考文章