備忘錄模式(Memento Pattern):是一種行為型設計模式,在不破壞封裝性的前提下,捕獲一個對象的內部狀態,併在該對象之外保存這個狀態。在JavaScript中,可以使用閉包來實現備忘錄模式。 備忘錄模式通常用於處理用戶界面的狀態。當用戶與應用程式交互時,應用程式會根據用戶的輸入更改其狀態。 ...
我們在開發一個功能是,經常會遇到從一個列表頁面,點擊列表項跳轉到詳情頁面的需求,理想的情況下,從詳情頁面返回到列表頁,應該回到跳轉前的狀態,可以繼續瀏覽其他內容;但是在沒做任何處理的情況下,返回列表頁後,列表頁會被刷新,回到初始的狀態,這就與我們的預設不符;為了實現這樣的需求,我們需要使用keep-alive組件來緩存列表頁面的狀態,讓其不被刷新,這樣就可以愉快的繼續瀏覽啦。
下麵就是我們使用keep-alive組件的一些步驟:(ps:項目使用vue3、vue-router、pinia、typescript)
1、聲明路由的Meta擴展數據類型,用來在路由中配置哪些頁面需要被緩存
router.d.ts
import "vue-router";
declare module "vue-router" {
interface RouteMeta extends MyRoute.RouteMeta {
title: string;
keepAlive?: boolean; // 該路由頁面是否需要緩存
keepAlivePages?: Array<string>; // 配置需要緩存的跳回來源(從某頁面回到該頁面,該頁面緩存,其他頁面回到該頁面,該頁面不緩存)
canBack?: boolean; // 是否可以返回
}
}
2、配置路由信息,以文章列表和詳情頁面為例
分模塊配置路由文件library.ts
import { RouteComponent, RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: "library/list",
name: "libraryList",
meta: { title: "文章列表", keepAlive: true, keepAlivePages: ["libraryDetail"] },
component: (): RouteComponent => {
return import(/* webpackChunkName: "pages_knowledge_library" */ "@/views/pages/knowledge/library/index.vue");
}
},
{
path: "library/detail",
name: "libraryDetail",
meta: { title: "文章詳情", canBack: true },
component: (): RouteComponent => {
return import(/* webpackChunkName: "pages_knowledge_library_detail" */ "@/views/pages/knowledge/library/detail.vue");
}
}
];
export default routes;
3、聲明頁面組件的名稱,與配置的名稱一致:keep-alive 組件會根據組件的 name 選項進行匹配,所以組件如果想要條件性地被 keep-alive 緩存,就必須顯式聲明一個 name 選項。
在 3.2.34 或以上的版本中,使用 <script setup> 的單文件組件會自動根據文件名生成對應的 name 選項,即使是在配合 <KeepAlive> 使用時也無需再手動聲明。
但在項目中我們是分模版開發,業務組件文件重名的幾率還是挺大的,所以我們還是需要顯式聲明組件的name屬性。
library/index.vue
<template>
<!-- 頁面模版 -->
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({ name: "libraryList" });
</script>
<script lang="ts" setup>
// 頁面綁定數據
<script>
library/detail.vue
<template>
<!-- 頁面模版 -->
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({ name: "libraryDetail" });
</script>
<script lang="ts" setup>
// 頁面綁定數據
<script>
3、定義一個Store用來緩存需要被緩存的頁面集合
keepAliveStore.ts
import { defineStore } from "pinia";
export interface KeepAliveState {
keepAliveComponents: Array<any>;
}
export const keepAliveStore = defineStore("keepAlive", {
state: (): KeepAliveState => {
return {
keepAliveComponents: []
};
},
getters: {
getKeepAliveComponents(state) {
return state.keepAliveComponents;
}
},
actions: {
// 加入到緩存隊列
setKeepAlive(component: any) {
if (!this.keepAliveComponents.includes(component)) {
this.keepAliveComponents.push(component);
}
},
// 從緩存隊列移除
removeKeepAlive(component: any) {
const index = this.keepAliveComponents.indexOf(component);
if (index !== -1) {
this.keepAliveComponents.splice(index, 1);
}
}
}
});
4、路由守衛處理:在路由進入之前讀取Meta擴展數據,如果配置了緩存,就加入到緩存隊列中;在路由離開之前判斷目標路由的Meta擴展數據,如果該頁面與配置的來源頁面不匹配,則將目標路由從緩存隊列中移除
router/index.ts
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes
});
// --- beforeRouter --- //
router.beforeEach((to, _from, next) => {
const { KeepAliveStore } = usePiniaStore();
const meta = to.meta || {};
// 加入緩存隊列
if (meta.keepAlive) {
KeepAliveStore.setKeepAlive(to.name);
}
// 做一些其他的事情
});
// ---afterRouter --- //
router.afterEach((to, from) => {
const { KeepAliveStore } = usePiniaStore();
// 如果配置了目標緩存,即:目標頁面不在目標緩存中,移除當前頁面緩存
if (from.meta.keepAlivePages) {
if (from.meta.keepAlivePages.indexOf(to.name as string) == -1) {
KeepAliveStore.removeKeepAlive(from.name);
}
}
// 做一些其他的事情
});
5、keep-alive組件的使用:取出緩存隊列中的集合,配置給include屬性,keep-alive組件便能把配置的路由頁面給緩存起來了;同時也可以設置max最大緩存數,避免緩存太多導致性能下降
RouterView.vue
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in" appear>
<keep-alive :include="keepAliveComponents" :max="10">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</template>
<script lang="ts" setup>
import { usePiniaStore } from "@/store";
const { KeepAliveStore } = usePiniaStore();
const keepAliveComponents = KeepAliveStore.getKeepAliveComponents;
</script>