SPA 別陷入路由死循環

在 vue 路由中,咱們會用導航守衛控制頁面的可否被進入查看,其中最後一步是調用 next() 函數,讓中斷的導航繼續進行vue

當一個導航觸發時,全局前置守衛按照建立順序調用。守衛是異步解析執行,此時導航在全部守衛 resolve 完以前一直處於 等待中。因此確保要調用 next 方法,不然鉤子就不會被 resolved

路由循環

關鍵點:vuex

next () 能夠傳入參數,實現跳轉到其餘頁面或者取消導航的功能。這時候要注意,不能堵住通道,要確保每個條件最後都能調用沒有傳遞參數的 next() 方法
不然,頁面會陷入無限刷新的死循環。由於傳入參數的 next() 方法會致使路由再次執行前置導航守衛,從而陷入循環。瀏覽器

例子1:驗證 token ,失敗則返回登陸頁cookie

router.beforeEach((to,from,next) =>{
  if (sessionStorage.getItem("token")) {
     if(to.path === "/login"){
       next({path:"/dashboard"})
     }
     else{
       alert("1")
       next()
     }     
  }else{
    next({path: "/login"})   // 會再次執行前置導航守衛,由於路徑變化
  }
})

上面的代碼表面看沒有問題:session

若是 sessionStorage 有 token,而且即將要進入的目標路徑是登錄頁,就跳轉到 /dashboard 頁,若是是其它的頁面,就直接進入異步

若是 sessionStorage 沒有 token 就跳轉到登錄頁async

可是代碼執行會引發死循環,緣由是沒有出口,執行 next({path: "/login"}) 會再次執行全局前置導航守衛。進入 /login 頁面前,就再次觸發守衛,一直重複進入ide

代碼改爲下面的就正常了:函數

router.beforeEach((to, from, next) => {
  let token = window.sessionStorage.getItem('token');
  if (to.path != '/login' && !token) {
    next({
      path: '/login'
    })
  } else {
    if (to.path == '/login' && token) {
      next('/dashboard')
    } else {     
      next()
    }
  }
})

例子2:動態添加路由spa

router.beforeEach((to, from, next) => {
  function getRouteAndMenu () {
    // 本地沒有保存可訪問路由,就須要計算
    store.dispatch('d2admin/user/GenerateRoutes') // 獲取可訪問路由,在 vuex 中保存
    router.addRoutes(store.state.d2admin.user.accessedRouters) // 和原有的固定路由合併到一塊兒
    const routeArray = routes.concat(store.state.d2admin.user.accessedRouters)
    // 處理路由 獲得每一級的路由設置
    store.commit('d2admin/page/init', routeArray)
    // 設置頂欄菜單
    store.dispatch('d2admin/menu/GenerateHeaderMenu', {
      role: store.state.d2admin.user.info.role,
      menuHeader
    }) 
    // 設置側邊欄菜單
    store.dispatch('d2admin/menu/GenerateMenu', {
      role: store.state.d2admin.user.info.role,
      menuAside
    }) 
    // 獲取側邊欄菜單,在 vuex 中保存
    store.dispatch('d2admin/menu/setMenuAside', {
      menuAside
    })
    next({ ...to, replace: true })  // hack 以確保路由增長後,再進行跳轉
  }

  if (from.name === null && to.name === '404') {
    // 避免刷新出現 404 頁面
    getRouteAndMenu()
  }
  
  // 驗證當前路由全部的匹配中是否須要有登陸驗證的
  if (to.matched.some(r => r.meta.auth)) {
    // 這裏暫時將cookie裏是否存有token做爲驗證是否登陸的條件
    // 請根據自身業務須要修改
    const token = util.cookies.get('token')
    if (token && token !== 'undefined') {
      const accessedRouters = store.state.d2admin.user.accessedRouters
      if (accessedRouters.length <= 0) {
        getRouteAndMenu()
      } else {
        next()
      }
    } else {
      // 沒有登陸的時候跳轉到登陸界面
      // 攜帶上登錄成功以後須要跳轉的頁面完整路徑
      next({
        name: 'login',
        query: {
          redirect: to.fullPath
        }
      })
    }
  } else {
    // 不須要身份校驗 直接經過
    next()
  }
})

上面的代碼,主要是實現計算動態路由並添加到原有路由裏、計算可展現的菜單欄,而後再跳轉到相應頁面的功能。

其中,next({ ...to, replace: true })方法是一個 hack 方法,能確保路由添加了再進行跳轉

問題在於,這裏只將動態計算的路由保存到了內存,因此在頁面刷新時,須要從新計算並添加,因此添加了下面的代碼:

if (from.name === null && to.name === '404') {
    // 避免刷新出現 404 頁面
    getRouteAndMenu()
}

而這會致使頁面一直在重複執行 getRouteAndMenu() 操做,瀏覽器會重複導航進入當前刷新的頁面,

緣由:getRouteAndMenu 除了 hack 方法沒有提供 next() 通道,每次 hack 都會再次執行導航守衛

解決方法:

不用 hack ,改爲使用 async await ,按順序執行,最後 next()

router.beforeEach((to, from, next) => {
  async function getRouteAndMenu () {
    // 本地沒有保存可訪問路由,就須要計算
    await store.dispatch('d2admin/user/GenerateRoutes') // 獲取可訪問路由,在 vuex 中保存
    router.addRoutes(store.state.d2admin.user.accessedRouters) // 和原有的固定路由合併到一塊兒
    const routeArray = routes.concat(store.state.d2admin.user.accessedRouters)
    // 處理路由 獲得每一級的路由設置
    store.commit('d2admin/page/init', routeArray)
    // 設置頂欄菜單
    await store.dispatch('d2admin/menu/GenerateHeaderMenu', {
      role: store.state.d2admin.user.info.role,
      menuHeader
    }) 
    // 設置側邊欄菜單
    await store.dispatch('d2admin/menu/GenerateMenu', {
      role: store.state.d2admin.user.info.role,
      menuAside
    }) 
    // 獲取側邊欄菜單,在 vuex 中保存
    await store.dispatch('d2admin/menu/setMenuAside', {
      menuAside
    })
    next()
  }

  if (from.name === null && to.name === '404') {
    // 避免刷新出現 404 頁面
    getRouteAndMenu()
  }
  
  // 驗證當前路由全部的匹配中是否須要有登陸驗證的
  if (to.matched.some(r => r.meta.auth)) {
    // 這裏暫時將cookie裏是否存有token做爲驗證是否登陸的條件
    // 請根據自身業務須要修改
    const token = util.cookies.get('token')
    if (token && token !== 'undefined') {
      const accessedRouters = store.state.d2admin.user.accessedRouters
      if (accessedRouters.length <= 0) {
        getRouteAndMenu()
      } else {
        next()
      }
    } else {
      // 沒有登陸的時候跳轉到登陸界面
      // 攜帶上登錄成功以後須要跳轉的頁面完整路徑
      next({
        name: 'login',
        query: {
          redirect: to.fullPath
        }
      })
    }
  } else {
    // 不須要身份校驗 直接經過
    next()
  }
})

另附路由導航守衛的調用順序

Vue Router 導航解析流程.jpg

相關文章
相關標籤/搜索