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

相关文章
相关标签/搜索