開始切入正題之前,有必要告知大家一下,這篇文章可能有一些深度,初學(xué)者可能理解會有些吃力。我會盡量把復(fù)雜問題簡單化,爭取讓每個閱讀的童鞋們都能看得懂。希望你對element-ui,vue-router,KeepAlive組件有一點(diǎn)了解。現(xiàn)在,我們開始吧。
熟悉element-ui的童鞋們都知道,ElMenuItem和ElSubMenu都需要一個index屬性,該屬性必須是唯一的。現(xiàn)在,我們想把路由配置直接應(yīng)用于ElMenu,該如何確保index的唯一性呢?我們需要有一個生成唯一index的函數(shù)。如下是genKey函數(shù)的定義,是不是很簡單?
export const genKey = ((k = 1) => () => k++)()
現(xiàn)在,我們創(chuàng)建addRouteMetaKey函數(shù),該函數(shù)對路由樹進(jìn)行遞歸遍歷,為每一個路由配置的meta屬性動態(tài)添加key字段。這個函數(shù)很簡單,屬于最基礎(chǔ)的遞歸使用例子,我就不做太多解釋了。
提示:數(shù)組的forEach方法不是純函數(shù),因?yàn)樗懈弊饔茫詅orEach方法不能稱之為函數(shù)式編程工具。
/** @param {import('vue-router').RouteRecordRaw[]} routes */
const addRouteMetaKey = routes => {
routes.forEach(route => {
if (route.meta) {
route.meta.key = genKey()
} else {
route.meta = { key: genKey() }
}
route.children && addRouteMetaKey(route.children)
})
return routes
}
通過addRouteMetaKey函數(shù),我們可以把路由的meta.key作為index的值了。
現(xiàn)在,我們想實(shí)現(xiàn)另一個功能,就是基于標(biāo)簽頁的路由組件緩存控制。使用過Vuejs KeepAlive組件的童鞋們,應(yīng)該多多少少都遇到一些坑吧?在我們的項(xiàng)目中,有一個頂部標(biāo)簽頁導(dǎo)航,每個tab項(xiàng)對應(yīng)一個url,以每個url對應(yīng)路由的title作為tab項(xiàng)標(biāo)題,當(dāng)切換tab的時候加載緩存中的url對應(yīng)的路由組件,關(guān)閉tab項(xiàng),下次打開該路由url,重新掛載對應(yīng)的路由組件,而不是從緩存中加載。
當(dāng)路由層級不確定的時候,KeepAlive會變的難以手動控制。所以,我劍走偏鋒,嘗試了一種簡單的解決方案,實(shí)踐證明:這是非常有效的。我的方案就是把無限層級的路由樹轉(zhuǎn)化為一維數(shù)組。通過為meta添加必要的字段,進(jìn)行頁面結(jié)構(gòu)個性化定制。
我們需要把每層路由配置的path轉(zhuǎn)化為全路徑,所以需要一個函數(shù):getRouteFullPath,該函數(shù)定義如下:
/**
* 獲取路由全路徑
* @description 如果path以 / 開頭(屬于絕對路徑),則直接返回,否則拼接路徑
* @param {string} path
* @param {string} parentPath
*/
export const getRouteFullPath = (path, parentPath) => {
return !parentPath || path.startsWith('/') ? path : joinPath(parentPath, path)
}
getRouteFullPath函數(shù)中使用到的joinPath函數(shù)用于將多個路徑字符串拼接為1個完整的路徑,定義如下:
/** @param {string[]} paths */
export const joinPath = (...paths) => {
const j = '/'
const [a, ...b] = paths
return (a.endsWith('/') ? a.replace(/(/+)$/gm, j) : a + j) + b.map(_ => _.replace(/^(/+)|(/+)$/gm, '')).join(j)
}
現(xiàn)在,我們把路由樹轉(zhuǎn)化為一維數(shù)組。我們定義toFlatRoutes函數(shù),該函數(shù)使用了數(shù)組的reduce方法對路由樹進(jìn)行聚合遞歸,將路由配置中的path屬性的值替換為全路徑,還順便給路由配置添加了name屬性,返回一個新的一維路由配置數(shù)組。這是函數(shù)式編程和遞歸結(jié)合的一個例子。
/**
* @param {import('vue-router').RouteRecordRaw[]} routes
* @returns {import('vue-router').RouteRecordRaw[]}
*/
const toFlatRoutes = (routes, parentPath) => {
return routes.reduce((t, r) => {
const path = getRouteFullPath(r.path, parentPath)
return [
...t,
...(r.children
? toFlatRoutes(r.children, path)
: [{ ...r, path, name: r.name || `name-${genKey()}` }]
)
]
}, [])
}
以上,我們解決了KeepAlive的緩存控制問題;現(xiàn)在,我們又有了一個新的用戶體驗(yàn)上的需求,就是我們想根據(jù)url對應(yīng)的路由,自動展開該路由meta.key所屬的側(cè)邊菜單;我們通過查閱element-ui文檔得知,ElMenu有一個open方法,接收index作為參數(shù),展開index對應(yīng)的菜單。
現(xiàn)在的問題是,我們的路由對應(yīng)的index是ElMenuItem的,而路由樹已經(jīng)被我們轉(zhuǎn)化為了一維數(shù)組,通過路由的matched字段是得不到我們想要的菜單index的。所以,我們需要遍歷原始路由樹。
如下代碼,我們定義getMenuKey函數(shù),該函數(shù)接收的參數(shù)為route對象。如果路由存在,我們進(jìn)行查找。首先進(jìn)行簡單查找,如果找到一個菜單menu,則返回該菜單的meta.key;如果簡單查找無果,則對路由樹進(jìn)行遞歸查找;這是函數(shù)式編程和遞歸結(jié)合的另一個例子。
/**
* @param {import('vue-router').RouteRecordRaw} item
* @returns {number|undefined}
*/
export const getMenuKey = item => {
if (item) {
const menu = state.menus.find(m => item.path.startsWith(m.path))
if (menu) {
return menu.meta.key
}
const itemKey = item.meta.key
return (function findFunc (menus) {
/** @param {import('vue-router').RouteRecordRaw} m */
const fn = m => m.meta.key === itemKey ? true : m.children && m.children.some(fn)
const curMenu = menus.find(fn)
return curMenu && curMenu.meta.key
})(state.menus) // state是響應(yīng)式對象,定義:const state = reactive({ menus: [] })
}
}
現(xiàn)在,我們大功告成了;以上就是本節(jié)的知識點(diǎn),童鞋們理解了嗎?只要我們熟悉遞歸的使用,其實(shí)操作樹很簡單。如果大家還有不懂的,可以評論區(qū)問我。感謝閱讀!