Vue Router 實(shí)現(xiàn)動(dòng)態(tài)路由和常見(jiàn)問(wèn)題及解決方法
個(gè)人理解:動(dòng)態(tài)路由不同于常見(jiàn)的靜態(tài)路由,可以根據(jù)不同的「因素」而改變站點(diǎn)路由列表。常見(jiàn)的動(dòng)態(tài)路由大都是用來(lái)實(shí)現(xiàn):多用戶(hù)權(quán)限系統(tǒng)不同用戶(hù)展示不同導(dǎo)航菜單。
如何利用Vue Router 實(shí)現(xiàn)動(dòng)態(tài)路由
Vue項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)路由的方式大體可分為兩種:
- 前端將全部路由規(guī)定好,登錄時(shí)根據(jù)用戶(hù)角色權(quán)限來(lái)動(dòng)態(tài)展示路由;
- 路由存儲(chǔ)在數(shù)據(jù)庫(kù)中,前端通過(guò)接口獲取當(dāng)前用戶(hù)對(duì)應(yīng)路由列表并進(jìn)行渲染;
第一種方式在很多Vue UI Admin上都實(shí)現(xiàn)了,可以去讀一下他們的源碼理解具體的實(shí)現(xiàn)思路,這里就不過(guò)多展開(kāi)。第二種方式現(xiàn)在來(lái)說(shuō)也比較常見(jiàn)了,因?yàn)榻陧?xiàng)目正好用到所以單獨(dú)講一下,這里我使用的方案是利用Vue Router的一些特性實(shí)現(xiàn)后端主導(dǎo)的動(dòng)態(tài)路由。
使用到的功能特性
Vue Router 全局前置守衛(wèi)
這里我們主要借助全局前置守衛(wèi)的「前置」特性,在頁(yè)面加載前將當(dāng)前用戶(hù)所用到的路由列表注入到Router實(shí)例中,注入使用到的方法則是下面的router.addRoutes方法。
Vue Router router.addRoutes 實(shí)例方法
router.addRoutes方法可以為Router實(shí)例動(dòng)態(tài)添加路由規(guī)則,剛好為我們實(shí)現(xiàn)動(dòng)態(tài)路由提供了注入方法。
Vue Router 路由懶加載
懶加載這個(gè)功能不是動(dòng)態(tài)路由的必要功能,但既然提供了這一特性,所以就直接在項(xiàng)目中使用了。
具體思路
基礎(chǔ)信息準(zhǔn)備
前端代碼實(shí)現(xiàn)基本靜態(tài)路由,例如:登錄頁(yè)路由,服務(wù)器錯(cuò)誤頁(yè)路由等(這里有一個(gè)坑,后面講)。數(shù)據(jù)庫(kù)存儲(chǔ)全部動(dòng)態(tài)路由信息。
數(shù)據(jù)庫(kù)如何存儲(chǔ)動(dòng)態(tài)路由信息?我選擇的方案是現(xiàn)將路由引用的對(duì)象字符串化,再將路由列表轉(zhuǎn)化為JSON格式傳輸給后端,經(jīng)后端處理后存儲(chǔ)到數(shù)據(jù)庫(kù)里??傊谇昂蠖诉M(jìn)行傳遞的是JSON格式的路由列表信息。
如何將路由中引用的對(duì)象字符串化?我遇到的實(shí)際問(wèn)題是:使用的UI組件提供了布局方案,需要引用布局組件并在子路由處引用具體頁(yè)面。我選擇的解決方案是:區(qū)別對(duì)待需要引用布局組件的component屬性,使用簡(jiǎn)短字符串代替布局組件,使用文件路徑字符串代替頁(yè)面引入。具體實(shí)現(xiàn)可以看后面的代碼實(shí)例。
利用全局前置守衛(wèi)對(duì)路由信息進(jìn)行判斷
1-判斷用戶(hù)是否登錄1.1-若未登錄,跳轉(zhuǎn)至登錄頁(yè)面1.2-若已經(jīng)登錄,判斷是否已獲取路由列表1.2.1-若未獲取,從后端獲取、解析并保存到Vuex中1.2.2-若已獲取,跳轉(zhuǎn)至目標(biāo)頁(yè)面
這里我沒(méi)做太多考察,直接將取到數(shù)據(jù)存儲(chǔ)到了Vuex中,在實(shí)際項(xiàng)目應(yīng)用的過(guò)程中應(yīng)考慮數(shù)據(jù)存儲(chǔ)的安全性。
如何實(shí)現(xiàn)路由列表解析?
- 將
JSON格式的路由信息解析為JavaScript列表對(duì)象; - 利用列表對(duì)象的
filter方法實(shí)現(xiàn)解析函數(shù),通過(guò)component判斷是否為布局組件; - 若為布局組件,使用布局組件代替
component字符串; - 若為具體頁(yè)面,使用
loadView函數(shù)加載對(duì)應(yīng)的具體頁(yè)面; - 利用 router.addRoutes 方法動(dòng)態(tài)添加路由
這一步就很簡(jiǎn)單了,將解析好的路由列表通過(guò)router.addRoutes方法添加到Router實(shí)例中即可。
簡(jiǎn)單的實(shí)現(xiàn)代碼
// router/index.js
import Vue from 'vue'
import store from '@/store'
import Router from 'vue-router'
import { getToken } from '@/lib/util'
Vue.use(Router)
// 定義靜態(tài)路由
const staticRoutes = [
{
path: '/login',
name: 'login',
meta: {
title: '登錄頁(yè)面',
hideInMenu: true
},
component: () => import('@/view/login/login.vue')
},
{
path: '/401',
name: 'error_401',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/401.vue')
},
{
path: '/500',
name: 'error_500',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/500.vue')
}
]
// 定義登錄頁(yè)面名稱(chēng)(為了方便理解才定義的)
const LOGIN_PAGE_NAME = 'login'
// 實(shí)例化 Router 對(duì)象
const router = new Router({
staticRoutes,
mode: 'history'
})
// 定義全局前置守衛(wèi)(里面有兩個(gè)坑要注意)
router.beforeEach((to, from, next) => {
// 通過(guò)自定義方法獲取用戶(hù) token 用來(lái)判斷用戶(hù)登錄狀態(tài)
const token = getToken()
if (!token && to.name !== LOGIN_PAGE_NAME) {
// 如果沒(méi)有登錄而且前往的頁(yè)面不是登錄頁(yè)面,跳轉(zhuǎn)到登錄頁(yè)
next({ name: LOGIN_PAGE_NAME })
} else if (!token && to.name === LOGIN_PAGE_NAME) {
// 如果沒(méi)有登錄而且前往的頁(yè)面是登錄頁(yè)面,跳轉(zhuǎn)到登錄頁(yè)面
// 這里有一個(gè)坑,一定要注意這一步和上一步得分開(kāi)寫(xiě)
// 如果把前兩步判斷合并為 if (!token) next({ name:login })
// 則會(huì)形成登錄頁(yè)面無(wú)限刷新的錯(cuò)誤,具體成因后面解釋
next()
} else {
// 如果登錄了
if (!store.state.app.hasGetRoute) {
// 如果沒(méi)有獲取路由信息,先獲取路由信息而后跳轉(zhuǎn)
store.dispatch('getRouteList').then(() => {
router.addRoutes(store.state.app.routeList)
// 這里也是一個(gè)坑,不能使用簡(jiǎn)單的 next()
// 如果直接使用 next() 刷新后會(huì)一直白屏
next({ ...to, replace: true })
})
} else {
// 如果已經(jīng)獲取路由信息,直接跳轉(zhuǎn)
next()
}
}
})
export default router
// store/index.js
import router from '@/router'
import Main from '@/components/main'
import { getToken } from '@/lib/util'
import { getRoute } from '@/api/app'
const loadView = (viewPath) => {
// 用字符串模板實(shí)現(xiàn)動(dòng)態(tài) import 從而實(shí)現(xiàn)路由懶加載
return () => import(`@/view/${viewPath}`)
}
const filterAsyncRouter = (routeList) => {
return routeList.map((route) => {
if (route.component) {
if (route.component === 'Main') {
// 如果 component = Main 說(shuō)明是布局組件
// 將真正的布局組件賦值給它
route.component = Main
} else {
// 如果不是布局組件就只能是頁(yè)面的引用了
// 利用懶加載函數(shù)將實(shí)際頁(yè)面賦值給它
route.component = loadView(route.component)
}
// 判斷是否存在子路由,并遞歸調(diào)用自己
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
}
})
}
export default {
state: {
routeList: [],
token: getToken(),
hasGetRoute: false
},
mutations: {
setRouteList(state, data) {
// 先將 JSON 格式的路由列表解析為 JavaScript List
// 再用路由解析函數(shù)解析 List 為真正的路由列表
state.routeList = filterAsyncRouter(JSON.parse(data))
// 修改路由獲取狀態(tài)
state.hasGetRoute = true
}
},
atcions: {
getRouteList({ state, commit }) {
return new Promise((resolve) => {
const token = state.token
getRoute({ token }).then((res) => {
let data = res.data.data
// 注意這里取出的是 JSON 格式的路由列表
commit('setRouteList', data)
resolve()
})
})
}
}
}
常見(jiàn)問(wèn)題
頁(yè)面卡在登錄頁(yè)面而且不斷刷新
這個(gè)問(wèn)題的解決方案在「實(shí)現(xiàn)代碼」中已經(jīng)提到了,只需要在判斷登錄狀態(tài)的時(shí)候注意不要將兩種未登錄狀態(tài)混為一談即可。但這樣治標(biāo)不治本,因?yàn)橥瑯拥膯?wèn)題可以由不同形式的代碼導(dǎo)致,那導(dǎo)致問(wèn)題的原因是什么那?然我們慢慢分析:
我們先假設(shè)不小心把兩種未登錄的狀態(tài)混在一起判斷:
if (!token) {
next({ name: LOGIN_PAGE_NAME })
}
這里的next({ name: LOGIN_PAGE_NAME })方法會(huì)再一次激活全局前置守衛(wèi),從而導(dǎo)致再一次進(jìn)入判斷并觸發(fā)next({ name: LOGIN_PAGE_NAME }),如此遞歸調(diào)用下去,頁(yè)面就會(huì)卡主并且不斷刷新。
動(dòng)態(tài)路由配合路由懶加載
實(shí)現(xiàn)這一目的的方案也在代碼示例中展示了:
const loadView = (viewPath) => {
return () => import(`@/view/${viewPath}`)
}
這里是運(yùn)用了一個(gè) JavaScript 不太常用的特性:字符串模板,使用此特性讓不支持字符串拼接的import操作能夠?qū)崿F(xiàn)動(dòng)態(tài)import不同的模塊。
動(dòng)態(tài)路由刷新后 404
這應(yīng)該是本方案中最常見(jiàn)的一個(gè)錯(cuò)誤之一,其原意是很多人在創(chuàng)建「基本靜態(tài)路由」的時(shí)候回把 404 頁(yè)面的路由也加入在里面,從而導(dǎo)致頁(yè)面加載初期動(dòng)態(tài)路由還沒(méi)有加入到路由實(shí)例中,匹配范圍最廣的 404 頁(yè)面就會(huì)跳出來(lái)。解決方法就是將 404 頁(yè)面的路由也加入到動(dòng)態(tài)路由中。
動(dòng)態(tài)路由刷新后變空白頁(yè)
造成這一問(wèn)題的原因有很多,我這里遇到的問(wèn)題是使用參考文章3解決的,但具體原理我還沒(méi)弄清楚,等我做一下研究再來(lái)更新。
動(dòng)態(tài)路由頁(yè)面刷新時(shí) Title 不穩(wěn)定
造成這一問(wèn)題的原因很簡(jiǎn)單:因?yàn)轫?yè)面刷新的時(shí)候路由信息還沒(méi)加載進(jìn)來(lái),所以根本沒(méi)有標(biāo)題信息可供加載。但是我還沒(méi)找到比較好的解決方案,同樣等我研究一下再更新。
參考大師兄:
Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)……
rambo:vue router 動(dòng)態(tài)路由 刷新后變空白頁(yè)
總結(jié)
到此這篇關(guān)于Vue Router 實(shí)現(xiàn)動(dòng)態(tài)路由和常見(jiàn)問(wèn)題及解決方法的文章就介紹到這了,更多相關(guān)vue router 動(dòng)態(tài)路由內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3 Teleport瞬間移動(dòng)函數(shù)使用方法詳解
這篇文章主要為大家詳細(xì)介紹了vue3 Teleport瞬間移動(dòng)函數(shù)使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
Vue?privide?和inject?依賴(lài)注入的使用詳解
這篇文章主要介紹了Vue?privide?和inject?依賴(lài)注入的用法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10
vue中本地靜態(tài)圖片的路徑應(yīng)該怎么寫(xiě)
這篇文章主要介紹了vue中本地靜態(tài)圖片的路徑應(yīng)該怎么寫(xiě),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
詳解VS Code使用之Vue工程配置format代碼格式化
這篇文章主要介紹了詳解VS Code使用之Vue工程配置format代碼格式化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
vue項(xiàng)目的訪問(wèn)端口及其設(shè)置方式
這篇文章主要介紹了vue項(xiàng)目的訪問(wèn)端口及其設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-10-10
淺析Vue3中通過(guò)v-model實(shí)現(xiàn)父子組件的雙向數(shù)據(jù)綁定及利用computed簡(jiǎn)化父子組件雙向綁定
這篇文章主要介紹了淺析Vue3中通過(guò)v-model實(shí)現(xiàn)父子組件的雙向數(shù)據(jù)綁定及利用computed簡(jiǎn)化父子組件雙向綁定,需要的朋友可以參考下2022-12-12
基于Vue實(shí)現(xiàn)電商SKU組合算法問(wèn)題
這篇文章主要介紹了基于Vue實(shí)現(xiàn)電商SKU組合算法問(wèn)題 ,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
基于Vue方法實(shí)現(xiàn)簡(jiǎn)單計(jì)時(shí)器
這篇文章主要為大家詳細(xì)介紹了基于Vue方法實(shí)現(xiàn)簡(jiǎn)單計(jì)時(shí)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

