背景介紹
在前端日常項(xiàng)目開發(fā)中,大家應(yīng)該都有遇到過這樣的需求:當(dāng)我們?cè)诹斜眄?yè)輸入條件進(jìn)行數(shù)據(jù)篩選時(shí),我們可能需要根據(jù)篩選后的結(jié)果進(jìn)入到某一條的詳情頁(yè),當(dāng)我們看完詳情頁(yè)內(nèi)容,再返回列表頁(yè),此時(shí)我們希望還是之前的篩選結(jié)果,包括條件和分頁(yè)等。
在實(shí)際開發(fā)中,改變頁(yè)面導(dǎo)航的路由都會(huì)重新匹配頁(yè)面,初始化頁(yè)面的數(shù)據(jù)狀態(tài),無法直接達(dá)到這種效果。但Vue給我們提供了“秘密武器”-keep-alive,可以實(shí)現(xiàn)頁(yè)面的數(shù)據(jù)緩存。
其實(shí)以前也寫過類似的實(shí)現(xiàn)頁(yè)面緩存的方案:Vue-Router 實(shí)現(xiàn)前端頁(yè)面緩存,是通過keep-alive的include屬性控制要緩存的頁(yè)面。但是這種方式不夠靈活,一旦緩存了頁(yè)面,從其他頁(yè)面(非詳情頁(yè)面)跳入到列表頁(yè)時(shí)數(shù)據(jù)也不會(huì)重新更新,需要在組件路由守衛(wèi)beforeRouteEnter中去做判斷處理,十分麻煩。
因此,經(jīng)過參考探索,今天來分享一個(gè)一勞永逸的實(shí)現(xiàn)頁(yè)面緩存的方案。
實(shí)現(xiàn)思路
總體思路:
只要是列表頁(yè)(需要使用緩存的頁(yè)面),進(jìn)入之前,都將其添加到要緩存的頁(yè)面中,離開列表頁(yè)時(shí)判斷新(to)路由是否是該列表頁(yè)指定的詳情頁(yè)(配置路由時(shí)通過 cacheTo 字段來指定),如果不是就清空緩存。
思路分析
根據(jù)總體思路,主要分為以下四種情況:
-
from和to都不是列表頁(yè)- 都不需要緩存,清空以前的緩存
-
from和to都是列表頁(yè)- 2.1-如果
to不在from的配置中,清空緩存,新增to緩存 - 2.2-如果
to在from的配置中,保留from緩存,新增to緩存
- 2.1-如果
-
to是列表頁(yè),from不是- 3.1-
from不在to的配置中,清空緩存,新增to緩存 - 3.2-
from在to的配置中,不做處理(因?yàn)?3.1 已經(jīng)緩存了 to)
- 3.1-
-
from是列表頁(yè),to不是- 4.1-
to不在from的配置中,清空緩存 - 4.2-
to在from的配置中,不做處理(因?yàn)?from 已經(jīng)在 3.1 時(shí)緩存)
- 4.1-
以上思路就是日常所有的場(chǎng)景,理解起來可能會(huì)比較繞,結(jié)合實(shí)際場(chǎng)景理解就會(huì)發(fā)現(xiàn)所有情況都已經(jīng)覆蓋到。
具體實(shí)現(xiàn)
前置須知
- 在路由表配置中維護(hù)一個(gè)
cacheTo字段,如果配置了該字段就表示該路由為列表頁(yè)。 - 控制緩存的邏輯,我們單獨(dú)在一個(gè)文件內(nèi)完成,作為組件單獨(dú)管理。為了避免
include的管理更加簡(jiǎn)單,就不適用vuex,這里使用Vue.observable。 - 為了避免維護(hù)
keep-alive的include屬性對(duì)代碼造成侵入,這里使用函數(shù)式組件。 - 為了在管理緩存的組件內(nèi)能獲取到
to和from,需要在組件內(nèi)注冊(cè)一個(gè)beforeEach鉤子,vue-router的beforeEach鉤子是可以重復(fù)注冊(cè)的,按照注冊(cè)順序執(zhí)行。
實(shí)現(xiàn)細(xì)節(jié)
// keep-alive-rorute.js
import Vue from "vue";
/**
* 創(chuàng)建存儲(chǔ)緩存?zhèn)}庫(kù)
*/
// 存儲(chǔ)需要緩存頁(yè)面路由
const cacheState = Vue.observable({
caches: [],
});
// 清理路由緩存
const clearCache = () => {
if (!cacheState.caches.length) return;
cacheState.caches = [];
};
// 新增路由緩存
const addCache = (name) => cacheState.caches.push(name);
const defaultHook = (to, from, next) => next();
// 路由進(jìn)入鉤子函數(shù)
export const beforeRouterEachHook = (hook = defaultHook) => {
return (to, from, next) => {
/**
* 思路:只要是類列表頁(yè),進(jìn)入之前都將其緩存,離開時(shí)判斷新路由是否是該類列表頁(yè)指定的類詳情頁(yè),如果不是就清除緩存。
*/
// to頁(yè)面路由
const toRouteName = to.name;
// to頁(yè)面為列表頁(yè)時(shí)配置的to路由集合
const toCacheRoutes = (to.meta || {}).cacheTo;
// to頁(yè)面是否是列表頁(yè)
const isToPageList = toCacheRoutes && toCacheRoutes.length;
// from頁(yè)面路由名稱
const fromRouteName = from.name;
// form頁(yè)面為列表頁(yè)時(shí),配置的to頁(yè)面路由集合
const fromCacheRoutes = (from.meta || {}).cacheTo;
// from頁(yè)面是否是列表頁(yè)
const isFromPageList = fromCacheRoutes && fromCacheRoutes.length;
// 1如果to,from都不是列表頁(yè),清除所有緩存
if (!isToPageList && !isFromPageList) {
clearCache();
} else if (isToPageList && isFromPageList) {
// 2如果to,from都是列表頁(yè)
// 如果to列表頁(yè)在from列表頁(yè)的配置中就要緩存,否則不用
if (fromCacheRoutes.indexOf(toRouteName) < 0) {
clearCache();
}
if (to.matched && to.matched.length > 2) {
// 三級(jí)路由時(shí),緩存父子兩級(jí)路由
to.matched.map((element) => {
if (element.name) {
addCache(element.name);
}
});
} else {
// 二級(jí)路由緩存當(dāng)前路由
addCache(toRouteName);
}
} else if (isToPageList) {
// 3如果to是列表頁(yè)
// 如果from不在to的配置中,清除所有緩存,同時(shí)緩存新路由
if (toCacheRoutes.indexOf(fromRouteName) < 0) {
clearCache();
if (to.matched && to.matched.length > 2) {
// 三級(jí)路由時(shí)緩存父子兩級(jí)路由
to.matched.map((element) => {
if (element.name) {
addCache(element.name);
}
});
} else {
// 二級(jí)路由緩存當(dāng)前路由
addCache(toRouteName);
}
}
} else if (isFromPageList) {
// 4如果from頁(yè)面是列表頁(yè)
// to不在from的配置中清空緩存
if (fromCacheRoutes.indexOf(toRouteName) < 0) {
clearCache();
}
}
return hook(to, from, next);
};
};
// 緩存路由組件
export const KeepAliveRoute = {
install(Vue) {
const component = {
name: "KeepAliveRoute",
functional: true,
render(h, params) {
return h(
"keep-alive",
{ props: { include: cacheState.caches } },
params.children
);
},
};
Vue.component("KeepAliveRoute", component);
},
};
項(xiàng)目使用
- 在
main.js中注冊(cè)組件并執(zhí)行緩存邏輯
// main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
// 引入
import { beforeRouterEachHook, KeepAliveRoute } from "./keep-alive-rorute";
// 注冊(cè)
Vue.use(KeepAliveRoute);
// 路由守衛(wèi)中執(zhí)行緩存邏輯
router.beforeEach(beforeRouterEachHook());
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
- 在需要緩存的路由展示區(qū)使用緩存組件
// Home.vue
<template>
<transition name="fade-transform" mode="out-in">
// 使用緩存組件
<KeepAliveRoute>
<router-view></router-view>
</KeepAliveRoute>
</transition>
</template>
- 在路由配置表中配置列表頁(yè)
[
{
path: "/list",
name: "List",
meta: {
// 配置需要進(jìn)入列表頁(yè)使用緩存數(shù)據(jù)的頁(yè)面
cacheTo: ["Detail"],
},
....
},
{
path: 'detail',
name: 'Detail',
......
},
];
注意
- 路由配置必須使用
name字段,且和對(duì)應(yīng)的組件name對(duì)應(yīng)。 - 支持多層級(jí)嵌套的父子路由緩存。只需要在父子組件的
router-view外面包上KeepAliveRoute組件即可。
查看更多內(nèi)容,請(qǐng)關(guān)注微信公眾號(hào):柒葷捌素