1. 程式人生 > 程式設計 >Vue Router 實現動態路由和常見問題及解決方法

Vue Router 實現動態路由和常見問題及解決方法

個人理解:動態路由不同於常見的靜態路由,可以根據不同的「因素」而改變站點路由列表。常見的動態路由大都是用來實現:多使用者許可權系統不同使用者展示不同導航選單。

如何利用Vue Router 實現動態路由

Vue專案實現動態路由的方式大體可分為兩種:

  • 前端將全部路由規定好,登入時根據使用者角色許可權來動態展示路由;
  • 路由儲存在資料庫中,前端通過介面獲取當前使用者對應路由列表並進行渲染;

第一種方式在很多Vue UI Admin上都實現了,可以去讀一下他們的原始碼理解具體的實現思路,這裡就不過多展開。第二種方式現在來說也比較常見了,因為近期專案正好用到所以單獨講一下,這裡我使用的方案是利用Vue Router

的一些特性實現後端主導的動態路由。

使用到的功能特性

Vue Router 全域性前置守衛

官網解釋

這裡我們主要藉助全域性前置守衛的「前置」特性,在頁面載入前將當前使用者所用到的路由列表注入到Router例項中,注入使用到的方法則是下面的router.addRoutes方法。

Vue Router router.addRoutes 例項方法

官網解釋

router.addRoutes方法可以為Router例項動態新增路由規則,剛好為我們實現動態路由提供了注入方法。

Vue Router 路由懶載入

官網解釋

懶載入這個功能不是動態路由的必要功能,但既然提供了這一特性,所以就直接在專案中使用了。

具體思路

基礎資訊準備

前端程式碼實現基本靜態路由,例如:登入頁路由,伺服器錯誤頁路由等(這裡有一個坑,後面講)。資料庫儲存全部動態路由資訊。

資料庫如何儲存動態路由資訊?我選擇的方案是現將路由引用的物件字串化,再將路由列表轉化為JSON格式傳輸給後端,經後端處理後儲存到資料庫裡。總之在前後端進行傳遞的是JSON格式的路由列表資訊。

如何將路由中引用的物件字串化?我遇到的實際問題是:使用的UI元件提供了佈局方案,需要引用佈局元件並在子路由處引用具體頁面。我選擇的解決方案是:區別對待需要引用佈局元件的component屬性,使用簡短字串代替佈局元件,使用檔案路徑字串代替頁面引入。具體實現可以看後面的程式碼例項。

利用全域性前置守衛對路由資訊進行判斷

1-判斷使用者是否登入1.1-若未登入,跳轉至登入頁面1.2-若已經登入,判斷是否已獲取路由列表1.2.1-若未獲取,從後端獲取、解析並儲存到Vuex中1.2.2-若已獲取,跳轉至目標頁面

這裡我沒做太多考察,直接將取到資料儲存到了Vuex中,在實際專案應用的過程中應考慮資料儲存的安全性。

如何實現路由列表解析?

  1. JSON格式的路由資訊解析為JavaScript列表物件;
  2. 利用列表物件的filter方法實現解析函式,通過component判斷是否為佈局元件;
  3. 若為佈局元件,使用佈局元件代替component字串;
  4. 若為具體頁面,使用loadView函式載入對應的具體頁面;
  5. 利用 router.addRoutes 方法動態新增路由

這一步就很簡單了,將解析好的路由列表通過router.addRoutes方法新增到Router例項中即可。

簡單的實現程式碼

// router/index.js
import Vue from 'vue'
import store from '@/store'
import Router from 'vue-router'
import { getToken } from '@/lib/util'

Vue.use(Router)

// 定義靜態路由
const staticRoutes = [
 {
 path: '/login',name: 'login',meta: {
 title: '登入頁面',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',component: () => import('@/view/error-page/500.vue')
 }
]

// 定義登入頁面名稱(為了方便理解才定義的)
const LOGIN_PAGE_NAME = 'login'

// 例項化 Router 物件
const router = new Router({
 staticRoutes,mode: 'history'
})

// 定義全域性前置守衛(裡面有兩個坑要注意)
router.beforeEach((to,from,next) => {
 // 通過自定義方法獲取使用者 token 用來判斷使用者登入狀態
 const token = getToken()
 if (!token && to.name !== LOGIN_PAGE_NAME) {
 // 如果沒有登入而且前往的頁面不是登入頁面,跳轉到登入頁
 next({ name: LOGIN_PAGE_NAME })
 } else if (!token && to.name === LOGIN_PAGE_NAME) {
 // 如果沒有登入而且前往的頁面是登入頁面,跳轉到登入頁面
 // 這裡有一個坑,一定要注意這一步和上一步得分開寫
 // 如果把前兩步判斷合併為 if (!token) next({ name:login })
 // 則會形成登入頁面無限重新整理的錯誤,具體成因後面解釋
 next()
 } else {
 // 如果登入了
 if (!store.state.app.hasGetRoute) {
 // 如果沒有獲取路由資訊,先獲取路由資訊而後跳轉
 store.dispatch('getRouteList').then(() => {
 router.addRoutes(store.state.app.routeList)
 // 這裡也是一個坑,不能使用簡單的 next()
 // 如果直接使用 next() 重新整理後會一直白屏
 next({ ...to,replace: true })
 })
 } else {
 // 如果已經獲取路由資訊,直接跳轉
 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) => {
 // 用字串模板實現動態 import 從而實現路由懶載入
 return () => import(`@/view/${viewPath}`)
}

const filterAsyncRouter = (routeList) => {
 return routeList.map((route) => {
 if (route.component) {
 if (route.component === 'Main') {
 // 如果 component = Main 說明是佈局元件
 // 將真正的佈局元件賦值給它
 route.component = Main
 } else {
 // 如果不是佈局元件就只能是頁面的引用了
 // 利用懶載入函式將實際頁面賦值給它
 route.component = loadView(route.component)
 }
 // 判斷是否存在子路由,並遞迴呼叫自己
 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
 // 再用路由解析函式解析 List 為真正的路由列表
 state.routeList = filterAsyncRouter(JSON.parse(data))
 // 修改路由獲取狀態
 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()
 })
 })
 }
 }
}

常見問題

頁面卡在登入頁面而且不斷重新整理

這個問題的解決方案在「實現程式碼」中已經提到了,只需要在判斷登入狀態的時候注意不要將兩種未登入狀態混為一談即可。但這樣治標不治本,因為同樣的問題可以由不同形式的程式碼導致,那導致問題的原因是什麼那?然我們慢慢分析:

我們先假設不小心把兩種未登入的狀態混在一起判斷:

if (!token) {
 next({ name: LOGIN_PAGE_NAME })
}

這裡的next({ name: LOGIN_PAGE_NAME })方法會再一次啟用全域性前置守衛,從而導致再一次進入判斷並觸發next({ name: LOGIN_PAGE_NAME }),如此遞迴呼叫下去,頁面就會卡主並且不斷重新整理。

動態路由配合路由懶載入

實現這一目的的方案也在程式碼示例中展示了:

const loadView = (viewPath) => {
 return () => import(`@/view/${viewPath}`)
}

這裡是運用了一個 JavaScript 不太常用的特性:字串模板,使用此特性讓不支援字串拼接的import操作能夠實現動態import不同的模組。

動態路由重新整理後 404

這應該是本方案中最常見的一個錯誤之一,其原意是很多人在建立「基本靜態路由」的時候回把 404 頁面的路由也加入在裡面,從而導致頁面載入初期動態路由還沒有加入到路由例項中,匹配範圍最廣的 404 頁面就會跳出來。解決方法就是將 404 頁面的路由也加入到動態路由中。

動態路由重新整理後變空白頁

造成這一問題的原因有很多,我這裡遇到的問題是使用參考文章3解決的,但具體原理我還沒弄清楚,等我做一下研究再來更新。

動態路由頁面重新整理時 Title 不穩定

造成這一問題的原因很簡單:因為頁面重新整理的時候路由資訊還沒載入進來,所以根本沒有標題資訊可供載入。但是我還沒找到比較好的解決方案,同樣等我研究一下再更新。

參考大師兄:

Vue 動態路由的實現……

Vue Router 文件頁面

rambo:vue router 動態路由 重新整理後變空白頁

總結

到此這篇關於Vue Router 實現動態路由和常見問題及解決方法的文章就介紹到這了,更多相關vue router 動態路由內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!