系統城裝機大師 - 唯一官網:www.farandoo.com!

當前位置:首頁 > 網絡編程 > JavaScript > 詳細頁面

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

時間:2020-03-06來源:電腦系統城作者:電腦系統城

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

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

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

  • 前端將全部路由規定好,登錄時根據用戶角色權限來動態展示路由;
  • 路由存儲在數據庫中,前端通過接口獲取當前用戶對應路由列表并進行渲染;

第一種方式在很多Vue UI Admin上都實現了,可以去讀一下他們的源碼理解具體的實現思路,這里就不過多展開。第二種方式現在來說也比較常見了,因為近期項目正好用到所以單獨講一下,這里我使用的方案是利用Vue Router的一些特性實現后端主導的動態路由。

使用到的功能特性

Vue Router 全局前置守衛

官網解釋

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

Vue Router router.addRoutes 實例方法

官網解釋

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

Vue Router 路由懶加載

官網解釋

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

具體思路

基礎信息準備

前端代碼實現基本靜態路由,例如:登錄頁路由,服務器錯誤頁路由等(這里有一個坑,后面講)。數據庫存儲全部動態路由信息。

數據庫如何存儲動態路由信息?我選擇的方案是現將路由引用的對象字符串化,再將路由列表轉化為JSON格式傳輸給后端,經后端處理后存儲到數據庫里??傊谇昂蠖诉M行傳遞的是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實例中即可。

簡單的實現代碼


 
  1. // router/index.js
  2. import Vue from 'vue'
  3. import store from '@/store'
  4. import Router from 'vue-router'
  5. import { getToken } from '@/lib/util'
  6.  
  7. Vue.use(Router)
  8.  
  9. // 定義靜態路由
  10. const staticRoutes = [
  11. {
  12. path: '/login',
  13. name: 'login',
  14. meta: {
  15. title: '登錄頁面',
  16. hideInMenu: true
  17. },
  18. component: () => import('@/view/login/login.vue')
  19. },
  20. {
  21. path: '/401',
  22. name: 'error_401',
  23. meta: {
  24. hideInMenu: true
  25. },
  26. component: () => import('@/view/error-page/401.vue')
  27. },
  28. {
  29. path: '/500',
  30. name: 'error_500',
  31. meta: {
  32. hideInMenu: true
  33. },
  34. component: () => import('@/view/error-page/500.vue')
  35. }
  36. ]
  37.  
  38. // 定義登錄頁面名稱(為了方便理解才定義的)
  39. const LOGIN_PAGE_NAME = 'login'
  40.  
  41. // 實例化 Router 對象
  42. const router = new Router({
  43. staticRoutes,
  44. mode: 'history'
  45. })
  46.  
  47. // 定義全局前置守衛(里面有兩個坑要注意)
  48. router.beforeEach((to, from, next) => {
  49. // 通過自定義方法獲取用戶 token 用來判斷用戶登錄狀態
  50. const token = getToken()
  51. if (!token && to.name !== LOGIN_PAGE_NAME) {
  52. // 如果沒有登錄而且前往的頁面不是登錄頁面,跳轉到登錄頁
  53. next({ name: LOGIN_PAGE_NAME })
  54. } else if (!token && to.name === LOGIN_PAGE_NAME) {
  55. // 如果沒有登錄而且前往的頁面是登錄頁面,跳轉到登錄頁面
  56. // 這里有一個坑,一定要注意這一步和上一步得分開寫
  57. // 如果把前兩步判斷合并為 if (!token) next({ name:login })
  58. // 則會形成登錄頁面無限刷新的錯誤,具體成因后面解釋
  59. next()
  60. } else {
  61. // 如果登錄了
  62. if (!store.state.app.hasGetRoute) {
  63. // 如果沒有獲取路由信息,先獲取路由信息而后跳轉
  64. store.dispatch('getRouteList').then(() => {
  65. router.addRoutes(store.state.app.routeList)
  66. // 這里也是一個坑,不能使用簡單的 next()
  67. // 如果直接使用 next() 刷新后會一直白屏
  68. next({ ...to, replace: true })
  69. })
  70. } else {
  71. // 如果已經獲取路由信息,直接跳轉
  72. next()
  73. }
  74. }
  75. })
  76.  
  77. export default router

 
  1. // store/index.js
  2. import router from '@/router'
  3. import Main from '@/components/main'
  4. import { getToken } from '@/lib/util'
  5. import { getRoute } from '@/api/app'
  6.  
  7. const loadView = (viewPath) => {
  8. // 用字符串模板實現動態 import 從而實現路由懶加載
  9. return () => import(`@/view/${viewPath}`)
  10. }
  11.  
  12. const filterAsyncRouter = (routeList) => {
  13. return routeList.map((route) => {
  14. if (route.component) {
  15. if (route.component === 'Main') {
  16. // 如果 component = Main 說明是布局組件
  17. // 將真正的布局組件賦值給它
  18. route.component = Main
  19. } else {
  20. // 如果不是布局組件就只能是頁面的引用了
  21. // 利用懶加載函數將實際頁面賦值給它
  22. route.component = loadView(route.component)
  23. }
  24. // 判斷是否存在子路由,并遞歸調用自己
  25. if (route.children && route.children.length) {
  26. route.children = filterAsyncRouter(route.children)
  27. }
  28. }
  29. })
  30. }
  31.  
  32. export default {
  33. state: {
  34. routeList: [],
  35. token: getToken(),
  36. hasGetRoute: false
  37. },
  38. mutations: {
  39. setRouteList(state, data) {
  40. // 先將 JSON 格式的路由列表解析為 JavaScript List
  41. // 再用路由解析函數解析 List 為真正的路由列表
  42. state.routeList = filterAsyncRouter(JSON.parse(data))
  43. // 修改路由獲取狀態
  44. state.hasGetRoute = true
  45. }
  46. },
  47. atcions: {
  48. getRouteList({ state, commit }) {
  49. return new Promise((resolve) => {
  50. const token = state.token
  51. getRoute({ token }).then((res) => {
  52. let data = res.data.data
  53. // 注意這里取出的是 JSON 格式的路由列表
  54. commit('setRouteList', data)
  55. resolve()
  56. })
  57. })
  58. }
  59. }
  60. }

常見問題

頁面卡在登錄頁面而且不斷刷新

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

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


 
  1. if (!token) {
  2. next({ name: LOGIN_PAGE_NAME })
  3. }

這里的next({ name: LOGIN_PAGE_NAME })方法會再一次激活全局前置守衛,從而導致再一次進入判斷并觸發next({ name: LOGIN_PAGE_NAME }),如此遞歸調用下去,頁面就會卡主并且不斷刷新。

動態路由配合路由懶加載

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


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

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

動態路由刷新后 404

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

動態路由刷新后變空白頁

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

動態路由頁面刷新時 Title 不穩定

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

參考大師兄:

Vue 動態路由的實現……

Vue Router 文檔頁面

rambo:vue router 動態路由 刷新后變空白頁

總結

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

分享到:

相關信息

系統教程欄目

欄目熱門教程

人氣教程排行

站長推薦

熱門系統下載

jlzzjlzz亚洲乱熟在线播放