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

全部缓存

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

  • a 页面
  1. <template>
  2. <div>
  3. <div>A页面</div>
  4. <input type="text" v-model="dataA" /><br />
  5. <div @click="toB">跳转B</div>
  6. <div @click="toC">跳转C</div>
  7. </div>
  8. </template>
  9. <script lang="ts" setup>
  10. import { ref } from "vue";
  11. import { useRouter, useRoute } from "vue-router";
  12. const router = useRouter();
  13. const route = useRoute();
  14. const dataA = ref("");
  15. const toB = () => {
  16. router.push("/bb");
  17. };
  18. const toC = () => {
  19. router.push("/cc");
  20. };
  21. </script>
  • b 页面
  1. <template>
  2. <div>
  3. <div>B页面</div>
  4. <input type="text" v-model="dataB" /><br />
  5. <div @click="toA">跳转A</div>
  6. </div>
  7. </template>
  8. <script lang="ts" setup>
  9. import { ref } from "vue";
  10. import { useRouter } from "vue-router";
  11. const router = useRouter();
  12. const dataB = ref("");
  13. const toA = () => {
  14. router.push("/aa");
  15. };
  16. </script>
  • c 页面
  1. <template>
  2. <div>
  3. <div>C页面</div>
  4. <input type="text" v-model="dataC" />
  5. <div @click="toA">跳转A</div>
  6. </div>
  7. </template>
  8. <script lang="ts" setup name="C">
  9. import { ref } from "vue";
  10. import { useRouter } from "vue-router";
  11. const router = useRouter();
  12. const dataC = ref("");
  13. const toA = () => {
  14. router.push("/aa");
  15. };
  16. </script>

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

  1. import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
  2. const routes: RouteRecordRaw[] = [
  3. {
  4. path: "/aa",
  5. name: "a",
  6. component: () => import(/* webpackChunkName: "A" */ "../views/a.vue"),
  7. },
  8. {
  9. path: "/bb",
  10. name: "b",
  11. component: () => import(/* webpackChunkName: "B" */ "../views/b.vue"),
  12. },
  13. {
  14. path: "/cc",
  15. name: "c",
  16. component: () => import(/* webpackChunkName: "C" */ "../views/c.vue"),
  17. },
  18. ];
  19. const router = createRouter({
  20. history: createWebHashHistory(),
  21. routes,
  22. });
  23. export default router;

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

  1. <template>
  2. <keep-alive>
  3. <router-view />
  4. </keep-alive>
  5. </template>

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

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

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

  1. <template>
  2. <router-view v-slot="{ Component }">
  3. <keep-alive>
  4. <component :is="Component" />
  5. </keep-alive>
  6. </router-view>
  7. </template>

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

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

缓存指定页面

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

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

  1. //route/index.ts
  2. const routes: RouteRecordRaw[] = [
  3. {
  4. path: "/aa",
  5. name: "a",
  6. meta: {
  7. keepAlive: true,
  8. },
  9. component: () => import(/* webpackChunkName: "A" */ "../views/a.vue"),
  10. },
  11. ...
  12. ];

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

  1. <template>
  2. <router-view v-slot="{ Component }">
  3. <keep-alive>
  4. <component v-if="$route.meta.keepAlive" :is="Component" />
  5. </keep-alive>
  6. <component v-if="!$route.meta.keepAlive" :is="Component" />
  7. </router-view>
  8. </template>

再看下效果

此时我们发现 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 中给组件命名可以这样写

  1. <script lang='ts'>
  2. export default {
  3. name: 'MyComponent',
  4. }
  5. </script>

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

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

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

  1. import { defineConfig, Plugin } from "vite";
  2. import vue from "@vitejs/plugin-vue";
  3. import vueSetupExtend from "vite-plugin-vue-setup-extend";
  4. export default defineConfig({
  5. plugins: [vue(), vueSetupExtend()],
  6. });

然后就可以这样命名了

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

下面我们修改一下 App.vue

  1. <template>
  2. <router-view v-slot="{ Component }">
  3. <keep-alive :include="['A']">
  4. <component :is="Component" />
  5. </keep-alive>
  6. </router-view>
  7. </template>

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

  1. npm i pinia

在 main.ts 中注册

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

新建 store/index.ts

  1. import { defineStore } from "pinia";
  2. export default defineStore("index", {
  3. state: (): { cacheRouteList: string[] } => {
  4. return {
  5. cacheRouteList: [],
  6. };
  7. },
  8. actions: {
  9. //添加缓存组件
  10. addCacheRoute(name: string) {
  11. this.cacheRouteList.push(name);
  12. },
  13. //删除缓存组件
  14. removeCacheRoute(name: string) {
  15. for (let i = this.cacheRouteList.length - 1; i >= 0; i--) {
  16. if (this.cacheRouteList[i] === name) {
  17. this.cacheRouteList.splice(i, 1);
  18. }
  19. }
  20. },
  21. },
  22. });

在 App.vue 中使用 cacheRouteList

  1. <template>
  2. <router-view v-slot="{ Component }">
  3. <keep-alive :include="catchStore.cacheRouteList">
  4. <component :is="Component" />
  5. </keep-alive>
  6. </router-view>
  7. </template>
  8. <script lang="ts" setup>
  9. import cache from "./store";
  10. const catchStore = cache();
  11. </script>

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

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

  1. import cache from "../store";
  2. const catchStore = cache();
  3. const router = useRouter();
  4. const toB = () => {
  5. catchStore.addCacheRoute("A");
  6. router.push("/bb");
  7. };
  8. const toC = () => {
  9. catchStore.removeCacheRoute("A");
  10. router.push("/cc");
  11. };

此时再看下页面的效果

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

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

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

  1. Vue中keep-alive组件的理解

    对keep-alive组件的理解 当在组件之间切换的时候,有时会想保持这些组件的状态,以避免反复重渲染导致的性能等问题,使用<keep-alive>包裹动态组件时,会缓存不活动的组件实例, ...

  2. vue中keepAlive的使用

    在开发中经常有从列表跳到详情页,然后返回详情页的时候需要缓存列表页的状态(比如滚动位置信息),这个时候就需要保存状态,要缓存状态:vue里提供了keep-alive组件用来缓存状态.可以用以下几种方案 ...

  3. vue中 keep-alive 组件的作用

    原文地址 在vue项目中,难免会有列表页面或者搜索结果列表页面,点击某个结果之后,返回回来时,如果不对结果页面进行缓存,那么返回列表页面的时候会回到初始状态,但是我们想要的结果是返回时这个页面还是之前 ...

  4. Vue中keep-alive的使用

    Vue中keep-alive的使用我总结的有两种方式应用: 首先简述一下keep-alive的作用,kee-alive可以缓存不活动的的组件.当组件之间进行相互切换的时候,默认会销毁,当重新切换回来时 ...

  5. vue3中的通过proxy实现双向数据绑定的原理

    1.什么是Proxy?它的作用是? 据阮一峰文章介绍:Proxy可以理解成,在目标对象之前架设一层 "拦截",当外界对该对象访问的时候,都必须经过这层拦截,而Proxy就充当了这种 ...

  6. 端午总结Vue3中computed和watch的使用

    1使用计算属性 computed 实现按钮是否禁用 我们在有些业务场景的时候,需要将按钮禁用. 这个时候,我们需要使用(disabled)属性来实现. disabled的值是true表示禁用.fals ...

  7. Vue3中的响应式对象Reactive源码分析

    Vue3中的响应式对象Reactive源码分析 ReactiveEffect.js 中的 trackEffects函数 及 ReactiveEffect类 在Ref随笔中已经介绍,在本文中不做赘述 本 ...

  8. Module Federation 模块联邦 在Vue3中使用Vue2搭建的微服务

    前言: 备注:本文基于对webpack Module Federation有一定了解的情况下 一般情况下使用模块联邦都是会使用相同的版本,如Vue2的组件时在Vue2中使用,但我为什么会在Vue3项目 ...

  9. vue3中pinia的使用总结

    pinia的简介和优势: Pinia是Vue生态里Vuex的代替者,一个全新Vue的状态管理库.在Vue3成为正式版以后,尤雨溪强势推荐的项目就是Pinia.那先来看看Pinia比Vuex好的地方,也 ...

  10. vue3中$attrs的变化与inheritAttrs的使用

    在vue3中的$attrs的变化 $listeners已被删除合并到$attrs中. $attrs现在包括class和style属性. 也就是说在vue3中$listeners不存在了.vue2中$l ...

随机推荐

  1. 2021-07-13:恢复二叉搜索树。给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出

    2021-07-13:恢复二叉搜索树.给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换.请在不改变其结构的情况下,恢复这棵树.进阶:使用 O(n) 空间复杂度的解法很容易实现.你能想出 ...

  2. Django4全栈进阶之路17 项目实战(用户管理):user_add.html用户新增画面设计

    1.模块: {% extends 'base.html' %} {% block content %} <div class="card mt-3"> <div ...

  3. 【工作随手记】并发之synchronized

    synchronized对于java同学肯定都是耳熟能详的必修课了.但是不管对于新手还是老手都有一些容易搞错的点.这里权做一点记录. 锁的是代码还是对象? 同步块一般有两种写法. 1是直接加以方法体上 ...

  4. Pandas 加载数据的方法和技巧

    哈喽大家好,我是咸鱼 相信小伙伴们在学习 python 数据分析的过程中或多或少都会听说或者使用过 pandas pandas 是 python 的一个拓展库,常用于数据分析 今天咸鱼将介绍几个关于 ...

  5. Java(包机制、doc、Scanner对象)

    包机制 本质:文件夹 用于区别类名的命名空间 一般利用公司域名倒置作为包名 import与通配符* 导入包 例: import java.util.Scanner; import com.xxx.xx ...

  6. 深入探究for...range语句

    1. 引言 在Go语言中,我们经常需要对数据集合进行遍历操作.对于数组来说,使用for语句可以很方便地完成遍历.然而,当我们面对其他数据类型,如map.string 和 channel 时,使用普通的 ...

  7. 使用C#编写.NET分析器-第二部分

    译者注 这是在Datadog公司任职的Kevin Gosse大佬使用C#编写.NET分析器的系列文章之一,在国内只有很少很少的人了解和研究.NET分析器,它常被用于APM(应用性能诊断).IDE.诊断 ...

  8. PostgreSQL 12 文档: PostgreSQL 服务端程序

    PostgreSQL 服务器应用   这一部分包含PostgreSQL服务器应用和支持工具的参考信息.这些命令只在数据库服务器所在的主机上运行才有用.其他工具程序在PostgreSQL 客户端应用中列 ...

  9. go NewTicker 得使用

    转载请注明出处: 在 Go 语言中,time.NewTicker 函数用于创建一个周期性触发的定时器.它会返回一个 time.Ticker 类型的值,该值包含一个通道 C,定时器会每隔一段时间向通道 ...

  10. Java扩展Nginx之三:基础配置项

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 经历了前面两篇的入门和编译源码之后,从本篇起 ...