首页 > 编程笔记 > JavaScript笔记 阅读:29

Vue KeepAlive用法详解(附带实例)

利用路由的异步懒加载,可以实现项目性能的部分提升。除此之外,其实还有一个方面可以被优化,就是缓存技术的应用!

在项目中使用缓存技术有很多种类别,包括数据级缓存、文件级缓存、请求级缓存、页面级缓存等。下面我们对页面级缓存进行一个简单的了解与探讨。

先思考一个问题,当前的项目利用路由实现了不同页面之间的衔接与切换,在后续的开发中页面的数量将会不断增多,那么会不会出现这样的场景:假设用户在操作某一个页面功能(这里为了便于理解将其称为第 1 个页面)时,想切换至另一个页面(将其称为第 2 个页面)先进行其他功能的处理,再处理第 3 个页面功能,最后回到第 1 个页面进行后续处理。这样的场景听起来不太符合逻辑,但是在实际操作中确实会发生。其实不仅有第 3 个页面,还可能会涉及第 4 个、第 5 个、第 6 个页面的切换操作。

相信读者对 Vue 组件与组件生命周期的概念已经非常清楚了,页面组件的存在是有生命周期的,从初始到销毁会有一个生命演化的阶段。而对于刚才所说的这种情况,会出现大量页面和大量组件不断地被创建、销毁,而这一处理过程是在计算机的内存中进行的,会大量消耗计算机的内存,严重影响项目的性能开销。

那么,如何才能解决当前内存被频繁消耗的问题呢?尽可能地减少页面组件在内存中的创建与销毁操作就是解决这一问题很好的思路!

如果让页面组件在创建完成后只在需要使用的时候使用,不需要使用的时候就在内存中休息,那么这样的结果是不是可以更好地实现性能的提升呢?这就是我们常说的用缓存路由组件来提升应用性能。

Vue 专门提供了 KeepAlive 组件来实现组件的缓存功能,下面我们来看看它的使用方法。

Vue KeepAlive的基本使用

在首页中引入生命周期。需要注意的是,这里引用 onActivated 和 onDeactivated 生命周期是为了方便测试。为了更好地观察各个生命周期,我们在每个生命周期中都进行了打印,同时为了方便与其他页面进行区分,在打印中都加上了 Home 首页的标识信息。

此时 views/Home.vue 文件代码如下:
<template>
  <div>首页</div>
</template>

<script setup>
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
} from 'vue';

// 初始
onBeforeMount(() => {
  console.log('Home onBeforeMount');
});

onMounted(() => {
  console.log('Home onMounted');
});

// 更新
onBeforeUpdate(() => {
  console.log('Home onBeforeUpdate');
});

onUpdated(() => {
  console.log('Home onUpdated');
});

// 销毁
onBeforeUnmount(() => {
  console.log('Home onBeforeUnmount');
});

onUnmounted(() => {
  console.log('Home onUnmounted');
});

// 激活
onActivated(() => {
  console.log('Home onActivated');
});

// 失活
onDeactivated(() => {
  console.log('Home onDeactivated');
});
</script>

为了验证多个页面之间的关系,还需要在用户页 Users.vue 中引入并使用生命周期,在生命周期中进行信息打印,同时添加 Users 的标识信息。

此时 views/user/Users.vue 文件代码如下:
<template>
  <div>
    <!-- 用户页的面包屑导航 -->
    <nav aria-label="breadcrumb">
      <ol class="breadcrumb">
        <li class="breadcrumb-item">
          <router-link to="/" class="text-decoration-none">首页</router-link>
        </li>
        <li class="breadcrumb-item active" aria-current="page">
          <router-link to="/users" class="text-decoration-none">
            用户</router-link>
        </li>
      </ol>
    </nav>
    <!-- 用户列表与请选择用户进行查看与编辑、查看用户详情、编辑用户是左、右两个独立的区域 -->
    <div class="row">
      <!-- 用户列表 -->
      <div class="col">
        <h1>用户列表</h1>
        <div class="list-group">
          <!-- 跳转到用户详情页的路由链接 -->
          <router-link
            v-for="n in 5"
            :key="n"
            :to="`/users/${n}`"
            class="list-group-item list-group-item-action"
            :class="{ active: n === currentId }"
            aria-current="n === currentId"
          >用户 {{ n }}</router-link>
        </div>
      </div>
      <!-- 请选择用户进行查看与编辑、查看用户详情、编辑用户 -->
      <div class="col">
        <!-- 嵌套子路由的占位渲染 -->
        <router-view name="users-alert"></router-view>
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script setup>
import {
  ref,
  watch,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
} from 'vue';
import { useRoute } from 'vue-router';
// 通过 useRoute 钩子函数声明当前 route 路由对象
const route = useRoute();
// 声明一个响应式数据作为当前被选中的用户 id
const currentId = ref(null);
// 利用 watch 监听实现点击不同用户链接时选中对应的用户
watch(
  () => route.params.id,
  (newVal) => {
    // 对当前被选中的用户 id 进行重新赋值
    // 将变化以后的新值在类型转化以后再赋给它
    currentId.value = +newVal;
  }
);
// 初始
onBeforeMount(() => {
  console.log('Users onBeforeMount');
});

onMounted(() => {
  console.log('Users onMounted');
});

// 更新
onBeforeUpdate(() => {
  console.log('Users onBeforeUpdate');
});

onUpdated(() => {
  console.log('Users onUpdated');
});

// 销毁
onBeforeUnmount(() => {
  console.log('Users onBeforeUnmount');
});

onUnmounted(() => {
  console.log('Users onUnmounted');
});

// 激活
onActivated(() => {
  console.log('Users onActivated');
});

// 失活
onDeactivated(() => {
  console.log('Users onDeactivated');
});
</script>
上面的代码只是添加了生命周期,并没有做任何与缓存相关的操作,此时是没有缓存的。从首页切换到用户页时,首页组件会被销毁,不会被缓存,也就是会执行 onBeforeUnmount 和 onUnmounted 回调。从用户页回到首页时,首页组件会被重新创建,并执行 onBeforeMount 与 onMounted 回调。

那么如何才能让页面进行缓存呢?其实 Vue 的内置组件 KeepAlive 可以实现缓存,KeepAlive 中文翻译为“保持活着”,代表在包裹组件时,会缓存不活动的组件实例,而不会销毁它们。此时打开根组件文件 App.vue,在 router-view 组件或者 component 动态组件外部添加 KeepAlive 组件进行包裹,以确保其包裹的页面组件能够保持活着的开启模式。

需要注意的是,从 Vue3 开始不再允许用 keep-alive 直接包裹 router-view,只允许包裹 component 动态组件。

KeepAlive 主要有 3 个属性,分别是 include(包含缓存页面)、exclude(排除缓存页面)和 max(最大缓存页面数量)。现在暂且不设置任何参数,直接刷新页面查看项目运行的效果。

此时 App.vue 文件代码如下:
<template>
  <div class="container">
    <router-view name="router-view-header"></router-view>
    <router-view v-slot="{ Component, route }">
      <transition
        mode="out-in"
        :enter-active-class="`animate__animated ${route.meta.enterActiveClass}`"
        :leave-active-class="`animate__animated ${route.meta.leaveActiveClass}`"
      >
        <keep-alive>
          <component :is="Component" />
        </keep-alive>
      </transition>
    </router-view>
    <router-view name="router-view-footer"></router-view>
  </div>
</template>
观察控制台,在首页加载时,除了触发初始阶段的两个生命周期钩子函数 onBeforeMount、onMounted,还会触发激活状态的 onActivated 生命周期钩子函数,如下图所示。


图 1 首页

从首页切换至用户页时,不会触发销毁阶段的生命周期钩子函数 onBeforeUnmount 与 onUnmounted,而是触发了失活状态的生命周期钩子函数 onDeactivated,如下图所示。


图 2 切换到用户页

从这点可以看出首页已经被缓存在内存中,有需要的时候可以随时再次启动应用。同样的道理,由于 KeepAlive 没有任何参数的限制,因此用户页也同样被应用了缓存技术,它触发的是激活状态的生命周期钩子函数 onActivated。

从用户页再次回到首页时,就是见证奇迹的时刻。通过观察控制台面板可以发现,首先用户页执行了失活状态的 onDeactivated 生命周期钩子函数,然后首页触发了更新阶段的 onBeforeUpdate、onUpdated 两个生命周期钩子函数,并且再次触发了激活状态的 onActivated 生命周期钩子函数。

不管是用户页还是首页,都被进行了页面缓存,没有再经历初始创建与最终销毁阶段,如下图所示:


图 3 首页和用户页都被进行了页面缓存

Vue KeepAlive的参数设置

使用 KeepAlive 进行页面缓存看起来十分方便,那么是否可以随意使用呢?答案是:不可以。任何事物都有好与不好两个方面,如果随意使用 KeepAlive 进行页面缓存,那么内存中将会积累大量无效的缓存页面而无法释放,同样会造成性能的下降。

KeepAlive 为开发者提供了 include、exclude 与 max 属性,让开发者可以有选择地控制需要缓存的页面。

include 与 exclude 是包含与排除的意思,但是需要包含哪些页面、排除哪些页面开发者得知道才行。在 Vue3 中,<script setup> 语法糖没有设置组件名称的属性,但显然 KeepAlive 组件属性使用的前提是:必须给操作的页面组件设置组件名称 name 属性。此时一个是需要,一个是没有,这就形成了矛盾,我们该如何解决呢?

其实我们可以给首页和用户页添加 Vue2 的 script 语法结构。比如在首页和用户页中利用 Vue2 语法添加组件的名称。需要注意的是,一个页面中可以包含多个 script 标签,而 setup 语法糖的代码结构不需要进行任何处理。

修改后的 views/Home.vue 文件代码如下:
<template>...</template>

<script>
export default {
  name: 'Home',
}
</script>
<script setup>...</script>

修改后的 views/user/Users.vue 文件代码如下:
<template>...</template>

<script>
export default {
  name: 'Users',
}
</script>
<script setup>...</script>
现在已经将没有变成了有,给 KeepAlive 添加 include 属性,其属性值利用字符串的方式将首页进行缓存设置,并不包含用户页。

此时按照之前的操作流程,即从首页到用户页再回到首页的顺序进行测试。读者会发现首页先是经过了初始与激活状态,然后失活到用户页,而从控制台中可以看出,用户页只经过了初始阶段,从用户页回到首页,则是从销毁状态变为更新和重新激活状态。可以看出我们已经成功将首页设置为了缓存页面。

此时 App.vue 文件代码如下:
<keep-alive include="Home">
  <component :is="Component" />
</keep-alive>
此时刷新页面,控制台如下图所示:


图 4 控制台

include 属性值除了可以设置为字符串,还可以设置为正则表达式和数组。下面通过一段代码进行演示:
<!-- 利用字符串实现首页与用户页的缓存页面包含 -->
<keep-alive include="Home,Users">
  <component :is="Component" />
</keep-alive>

<!-- 利用正则表达式实现首页与用户页的缓存页面包含 -->
<keep-alive :include="/Home|Users/">
  <component :is="Component" />
</keep-alive>

<!-- 利用数组实现首页与用户页的缓存页面包含 -->
<keep-alive :include="[ 'Home', 'Users' ]">
  <component :is="Component" />
</keep-alive>
此时读者已经对 include 的属性值有了一定的了解,那么对于 exclude 属性的设置,读者也能很快理解。它与 include 是反向含义,但属性设置、类型和模式都是相同的。

KeepAlive 还有一个 max 属性,这个属性的功能是限制缓存的页面数量。如果缓存的页面超过限制的数量,那么 KeepAlive 会将前面的缓存页面进行销毁处理,以确保内存中只保留最新缓存的页面。

相关文章