在 vue 中,預設情況下,一個組件實例在被替換掉後會被銷毀。這會導致它丟失其中所有已變化的狀態——當這個組件再一次被顯示時,會創建一個只帶有初始狀態的新實例。但是 vue 提供了 keep-alive 組件,它可以將一個動態組件包裝起來從而實現組件切換時候保留其狀態。本篇文章要介紹的並不是它的基本 ...
在 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>
啟動項目,測試一下頁面狀態有沒有被緩存
此時我們發現狀態並沒有緩存,並且控制台還給了個警告
上面的寫法在 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 包裹一個動態組件(回歸原始寫法)。
我們再試一下頁面的緩存效果,這時候發現頁面的狀態被緩存了
緩存指定頁面
通常情況下我們並不想將所有頁面狀態都緩存,而只想緩存部分頁面,這樣的話該怎麼做呢?
其實我們可以在 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>
再看下效果
此時我們發現 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");
};
此時再看下頁面的效果
可以發現 a 到 c 後再回來狀態就重置了,這樣不僅做到了上述效果,還可以讓你隨時隨地的去刪除指定組件的緩存。
到這裡我們便完成了使用 inclue 對頁面狀態緩存進行更精細化的控制。當然,如果你有更好的方案歡迎在評論區指出,一起討論探索