Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?

  • Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?已关闭评论
  • 135 次浏览
  • A+
所属分类:Web前端
摘要

在 vue 中,默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。但是 vue 提供了 keep-alive 组件,它可以将一个动态组件包装起来从而实现组件切换时候保留其状态。本篇文章要介绍的并不是它的基本使用方法(这些官网文档已经写的很清楚了),而是它如何结合 VueRouter 来更自由的控制页面状态的缓存

在 vue 中,默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。但是 vue 提供了 keep-alive 组件,它可以将一个动态组件包装起来从而实现组件切换时候保留其状态。本篇文章要介绍的并不是它的基本使用方法(这些官网文档已经写的很清楚了),而是它如何结合 VueRouter 来更自由的控制页面状态的缓存

全部缓存

我们先搭建一个 Vue 项目,里面有三个页面a,b,c,并给它们一些相互跳转的逻辑和状态

  • a 页面
<template>   <div>     <div>A页面</div>     <input type="text" v-model="dataA" /><br />     <div @click="toB">跳转B</div>     <div @click="toC">跳转C</div>   </div> </template>  <script lang="ts" setup> import { ref } from "vue"; import { useRouter, useRoute } from "vue-router"; const router = useRouter(); const route = useRoute(); const dataA = ref(""); const toB = () => {   router.push("/bb"); }; const toC = () => {   router.push("/cc"); }; </script> 
  • b 页面
<template>   <div>     <div>B页面</div>     <input type="text" v-model="dataB" /><br />     <div @click="toA">跳转A</div>   </div> </template>  <script lang="ts" setup> import { ref } from "vue"; import { useRouter } from "vue-router"; const router = useRouter(); const dataB = ref(""); const toA = () => {   router.push("/aa"); }; </script> 
  • c 页面
<template>   <div>     <div>C页面</div>     <input type="text" v-model="dataC" />     <div @click="toA">跳转A</div>   </div> </template>  <script lang="ts" setup name="C"> import { ref } from "vue"; import { useRouter } from "vue-router"; const router = useRouter(); const dataC = ref(""); const toA = () => {   router.push("/aa"); }; </script> 

然后在 route/index.ts 写下它们对应的路由配置

import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";  const routes: RouteRecordRaw[] = [   {     path: "/aa",     name: "a",     component: () => import(/* webpackChunkName: "A" */ "../views/a.vue"),   },   {     path: "/bb",     name: "b",     component: () => import(/* webpackChunkName: "B" */ "../views/b.vue"),   },   {     path: "/cc",     name: "c",     component: () => import(/* webpackChunkName: "C" */ "../views/c.vue"),   }, ];  const router = createRouter({   history: createWebHashHistory(),   routes, });  export default router; 

在 App.vue 中我们用 keep-alive 将 router-view 进行包裹

<template>   <keep-alive>     <router-view />   </keep-alive> </template> 

启动项目,测试一下页面状态有没有被缓存

Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?

此时我们发现状态并没有缓存,并且控制台还给了个警告

Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?

上面的写法在 vue2 中是可以的,但是在 vue3 中需要将 keep-alive 写在 router-view 中才行,我们修改一下写法

<template>   <router-view v-slot="{ Component }">     <keep-alive>       <component :is="Component" />     </keep-alive>   </router-view> </template> 

这种写法其实就是 router-view 组件的插槽传递了一个带有当前组件的组件名 Component 的对象,然后用 keep-alive 包裹一个动态组件(回归原始写法)。

我们再试一下页面的缓存效果,这时候发现页面的状态被缓存了

Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?

缓存指定页面

通常情况下我们并不想将所有页面状态都缓存,而只想缓存部分页面,这样的话该怎么做呢?

其实我们可以在 template 中通过$route 获取路由的信息,所以我们可以在需要缓存的页面配置一下 meta 对象,比如 a 页面我们想缓存其状态,可以将 keepAlive 设置位 true

//route/index.ts  const routes: RouteRecordRaw[] = [   {     path: "/aa",     name: "a",     meta: {       keepAlive: true,     },     component: () => import(/* webpackChunkName: "A" */ "../views/a.vue"),   },   ... ]; 

然后回到 App.vue 中判断 keepAlive 来决定是否缓存

<template>   <router-view v-slot="{ Component }">     <keep-alive>       <component v-if="$route.meta.keepAlive" :is="Component" />     </keep-alive>     <component v-if="!$route.meta.keepAlive" :is="Component" />   </router-view> </template> 

再看下效果

Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?

此时我们发现 a 页面状态被缓存,b 页面的状态没有缓存

但是有时候我们想要这样一个效果

a 跳转 b 的时候我们需要缓存 a 页面状态,但是当 a 跳转 c 的时候我们不需要缓存 a 页面,此时我们该如何做呢?

或许有的同学想到了这样一个方法,当 a 跳转 c 的时候将 a 页面的缓存删除,这样就实现了上面的效果。可惜我找了半天也没找到 vue3 中删除指定页面缓存的方法

我也尝试过跳转 c 页面的时候将 a 的 keepAlive 设置为 false,但是再次回到 a 页面的时候 keepAlive 会重置,a 页面状态依然会被缓存。

既然如此为了做到更精细的缓存控制只有使用 keep-alive 中的 inclue 属性了

使用 inclue 控制页面缓存

keep-alive 默认会缓存内部的所有组件实例,但我们可以通过 include 来定制该行为。它的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是一个数组。这里我们使用一个数组来维护需要缓存的组件页面,注意这个数组中是组件的名字而不是路由的 name

在 vue3 中给组件命名可以这样写

<script lang='ts'> export default {     name: 'MyComponent', } </script> 

但是我们通常会使用 setup 语法,这样的话我们得写两个script标签,太麻烦。我们可以使用插件vite-plugin-vue-setup-extend处理

npm i vite-plugin-vue-setup-extend -D 

然后在vite.config.ts中引入这个插件就可以使用了

import { defineConfig, Plugin } from "vite"; import vue from "@vitejs/plugin-vue"; import vueSetupExtend from "vite-plugin-vue-setup-extend";  export default defineConfig({   plugins: [vue(), vueSetupExtend()], }); 

然后就可以这样命名了

<script lang="ts" setup name="A"></script> 

下面我们修改一下 App.vue

<template>   <router-view v-slot="{ Component }">     <keep-alive :include="['A']">       <component :is="Component" />     </keep-alive>   </router-view> </template> 

这其实就代表组件名为 A 的 页面才会被缓存,接下来我们要做的就是控制这个数组来决定页面的缓存,但是这个数组要放在哪里维护呢? 答案肯定是放到全局状态管理器中拉。所以我们引入 Pinia 作为全局状态管理器

npm i pinia 

在 main.ts 中注册

import { createPinia } from "pinia"; const Pinia = createPinia(); createApp(App).use(route).use(Pinia).use(RouterViewKeepAlive).mount("#app"); 

新建 store/index.ts

import { defineStore } from "pinia";  export default defineStore("index", {   state: (): { cacheRouteList: string[] } => {     return {       cacheRouteList: [],     };   },   actions: {     //添加缓存组件     addCacheRoute(name: string) {       this.cacheRouteList.push(name);     },     //删除缓存组件     removeCacheRoute(name: string) {       for (let i = this.cacheRouteList.length - 1; i >= 0; i--) {         if (this.cacheRouteList[i] === name) {           this.cacheRouteList.splice(i, 1);         }       }     },   }, }); 

在 App.vue 中使用 cacheRouteList

<template>   <router-view v-slot="{ Component }">     <keep-alive :include="catchStore.cacheRouteList">       <component :is="Component" />     </keep-alive>   </router-view> </template> <script lang="ts" setup> import cache from "./store"; const catchStore = cache(); </script> 

此时就可以根据 cacheRouteList 控制缓存页面了。

此时我们再来实现前面提到的问题a 跳转 b 的时候我们需要缓存 a 页面状态,但是当 a 跳转 c 的时候我们不需要缓存 a 页面就很简单了

import cache from "../store"; const catchStore = cache(); const router = useRouter();  const toB = () => {   catchStore.addCacheRoute("A");   router.push("/bb"); }; const toC = () => {   catchStore.removeCacheRoute("A");   router.push("/cc"); }; 

此时再看下页面的效果

Vue3 中 keepAlive 如何搭配 VueRouter 来更自由的控制页面的状态缓存?

可以发现 a 到 c 后再回来状态就重置了,这样不仅做到了上述效果,还可以让你随时随地的去删除指定组件的缓存。

到这里我们便完成了使用 inclue 对页面状态缓存进行更精细化的控制。当然,如果你有更好的方案欢迎在评论区指出,一起讨论探索