Vue3動態(tài)路由KeepAlive設(shè)計方案(附詳細(xì)代碼)
1. 組件設(shè)置 name
核心:keep-alive 只認(rèn)組件 name,所以 cachedViews 或 include 必須用組件 name,而不是路由 name。
1. defineComponent模式
<script lang="ts">
import { defineComponent} from 'vue';
export default defineComponent({
name: 'CustomName'
})
</script>
2. setup語法糖
需要單獨(dú)新增一個
script標(biāo)簽
// 頁面為 TSX 時,需將 <script> 標(biāo)簽改為 lang="tsx"
<script lang="ts">
export default {
name: 'CustomName',
inheritAttrs: false,
customOptions: {}
}
</script>
Vue 3.3 中新引入了
defineOptions
defineOptions({
name: 'CustomName',
inheritAttrs: false,
// ... 更多自定義屬性
})
3. 借助插件
2. 動態(tài)生成組件 name 的實(shí)踐方案
相較于前端存儲全量路由結(jié)合接口權(quán)限過濾的方案,此方案借助 Vue2 createCustomComponent 的思路:
- 每條路由都會生成一個獨(dú)立的組件對象,并為其分配唯一的
name - 在動態(tài)生成組件時,將組件的
name設(shè)置為基于路由path處理后的安全名稱
1. createCustomComponent
import { defineAsyncComponent, defineComponent, h } from "vue";
/**
* @param {String} name 組件名稱 (必須與 keep-alive 的 include 匹配)
* @param {Function} componentLoader 即 () => import(...)
*/
export function createCustomComponent(name: string, componentLoader: any) {
// 1. 檢查 componentLoader 是否存在
if (!componentLoader || typeof componentLoader !== "function") {
console.error(`[路由錯誤]: 找不到組件 Name: ${name}`);
// 返回一個同步的錯誤占位組件
return defineComponent({ render: () => h("div", `組件 ${name} 加載失敗`) });
}
// 2. 將 Vite 的加載函數(shù)包裝成異步組件
const AsyncComp = defineAsyncComponent({
loader: componentLoader, // componentLoader 已經(jīng)是 () => import(...)
// 如果需要,可以在這里配置 loadingComponent
});
// 3. 直接返回渲染函數(shù),渲染異步組件
return defineComponent({
name, // Keep-Alive 通過這個 name 識別緩存
setup() {
// 保持 AsyncComp 為直接子級
return () => h(AsyncComp);
},
});
}
2. 組件名轉(zhuǎn)化
// 將 path 轉(zhuǎn)成合法的組件名,避免 '/' 等字符
function getComponentNameByPath(path: string) {
return path.replace(/\//g, '-').replace(/^-/, '');
}
3. 路由接入示例
component: () => import("@/views/dashboard/index.vue")
// 調(diào)整為
component: createCustomComponent("Dashboard", import("@/views/dashboard/index.vue"))
3. 通用組件緩存策略
疑問:如果共用一個組件來進(jìn)行創(chuàng)建、編輯、詳情,怎么根據(jù)路徑進(jìn)行匹配?
假設(shè)路徑是:/banner-list/banner-create、/banner-list/banner-edit、/banner-list/banner-detail 需要先進(jìn)行路徑命中匹配,無法命中則直接進(jìn)行默認(rèn)匹配:
- 先解析上層路徑,找到文件所在位置
- 再進(jìn)行精準(zhǔn)匹配,比如:公共組件統(tǒng)一命名為:basic-component
// 掃描views目錄下的vue文件
const modules = import.meta.glob("@/views/**/**.vue");
// 全局需要 keepAlive 的 path 列表
const keepAliveRoutes: string[] = [];
/**
* 解析后端返回的路由數(shù)據(jù)并轉(zhuǎn)換為 Vue Router 兼容的路由配置
*
* @param rawRoutes 后端返回的原始路由數(shù)據(jù)
* @returns 解析后的路由配置數(shù)組
*/
const parseDynamicRoutes = (rawRoutes: RawRoute[]): RouteRecordRaw[] => {
const parsedRoutes: RouteRecordRaw[] = [];
rawRoutes.forEach(item => {
const childrenColumn: RouteRecordRaw = {
path: item.path,
name: item.name,
component: Layout,
meta: {
title: item.name,
icon: item.icon || iconMap[item.path],
},
children: [] as RouteRecordRaw[],
};
if (item.children?.length) {
childrenColumn.redirect = item.children[0].path;
item.children.forEach(v => {
childrenColumn.children.push({
path: v.path,
name: getComponentNameByPath(v.path),
meta: {
title: v.name,
// 滿足條件的path開啟 keepAlive
keepAlive: keepAliveRoutes.includes(v.path),
// 取二級路由為高亮,兼容二、三級路由匹配
activeMenu: v.path.match(/^/[^/]+/[^/]+/)?.[0],
},
component: createCustomComponent(
getComponentNameByPath(v.path),
modules[`/src/views${v.path}/index.vue`],
),
});
});
}
parsedRoutes.push(childrenColumn);
});
return parsedRoutes;
};
4. Vue2 createCustomComponent
// 將 path 轉(zhuǎn)成合法的組件名,避免 '/' 等字符
function getComponentNameByPath(path) {
return path.replace(/\//g, '-').replace(/^-/, '');
}
/**
* @param {String} name 組件自定義名稱
* @param {Component | Promise<Component>} component
*/
export function createCustomComponent(name, component) {
return {
name,
data() {
return {
// 這里的 component 指向解析后的組件對象
component: null
};
},
async created() {
// 這里的 component 指向傳入的參數(shù)(可能是 Promise)
if (component instanceof Promise) {
try {
const res = await component;
this.component = res.default || res;
} catch (error) {
console.error(`無法解析組件 ${name}:`, error);
}
} else {
this.component = component;
}
},
render(h) {
if (!this.component) return null;
// 只負(fù)責(zé)渲染組件,不傳遞任何東西
return h(this.component);
}
};
}
// 調(diào)整路由文件獲取
import NotFound from '@/components/NotFound';
const pagesContext = require.context('@/pages', true, /.vue$/);
function resolveComponent(path) {
const directPath = `.${path}.vue`;
const indexPath = `.${path}/index.vue`;
let modulePath = null;
if (pagesContext.keys().includes(directPath)) {
modulePath = directPath;
} else if (pagesContext.keys().includes(indexPath)) {
modulePath = indexPath;
}
// 返回懶加載函數(shù)
if (modulePath) {
return () => Promise.resolve(pagesContext(modulePath).default);
}
// 找不到文件
console.warn(`[router error] 頁面未找到: ${path}`);
return () => Promise.resolve({ render: h => h(NotFound, { props: { path } }) });
}
// 組件匹配示例代碼
item.children.forEach(v => {
childrenColumn.children.push({
path: v.path,
name: getComponentNameByPath(v.path),
meta: {
title: v.name,
// 滿足條件的path開啟 keepAlive
keepAlive: keepAliveRoutes.includes(v.path),
// 取二級路由為高亮,兼容二、三級路由匹配
activeMenu: v.activeMenu || v.path.match(/^\/[^/]+\/[^/]+/)?.[0]
},
component: createCustomComponent(getComponentNameByPath(v.path), resolveComponent(v.path))
});
});
總結(jié)
到此這篇關(guān)于Vue3動態(tài)路由KeepAlive設(shè)計方案的文章就介紹到這了,更多相關(guān)Vue3動態(tài)路由KeepAlive內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue 數(shù)據(jù)響應(yīng)式相關(guān)總結(jié)
這篇文章主要介紹了Vue 數(shù)據(jù)響應(yīng)式的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)vue框架的使用,感興趣的朋友可以了解下2021-01-01
vue實(shí)現(xiàn)導(dǎo)出word文檔功能實(shí)例(含多張圖片)
項(xiàng)目需要導(dǎo)出word,于是乎又是查閱資料,然后自己寫,下面這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)導(dǎo)出word文檔功能(含多張圖片)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
vue + element ui實(shí)現(xiàn)播放器功能的實(shí)例代碼
這篇文章主要介紹了vue + element ui實(shí)現(xiàn)播放器功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
vue elementUI select下拉框設(shè)置默認(rèn)值(賦值)失敗的解決
這篇文章主要介紹了vue elementUI select下拉框設(shè)置默認(rèn)值(賦值)失敗的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10

