Vue路由守卫

Vue路由守卫

一、vue 路由定义

在 vue 中可以使用 router.beforeEach 注册一个全局前置守卫:

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
  // ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

二、路由说明

beforeEach((to, from, next) => {
    to      // 要去的路由
    from    // 当前路由
    next()  // 放行的意思
}

但是常常能看到 next(‘/login’) 、 next(to) 或者 next({ …to, replace: true }) 这又是啥意思呢?

其实在路由守卫中,只有next()是放行,其他的诸如:next(‘/logon’) 、 next(to) 或者 next({ …to, replace: true }) 都不是放行,而是:中断当前导航,执行新的导航。

可以这么理解:next() 是放行,但是如果next()里有参数的话,next()就像被重载一样,就有了不同的功能。

示例,有一个守卫,在守卫中我使用next(‘/logon’)

beforeEach((to, from, next) => {
  next('/logon')
}

执行时

beforeEach((to, from, next) => {
  beforeEach(('/logon', from, next) => {
       beforeEach(('/logon', from, next) => {
           beforeEac...  // 一直循环下去...... , 因为我们没有使用 next() 放行
      }
  }
}

如果把这个守卫改一下,当我在地址栏输入/home时

beforeEach((to, from, next) => {
   if(to.path === '/home') {
       next('/logon')
   } else {
    // 如果要去的地方不是 /home ,就放行
       next()
   }
}

注意:重点就在这,next(‘/logon’) 不是说直接去 /logon 路由,而是中断这一次路由守卫的操作,又进入一次路由守卫,就像嵌套一样,一层路由守卫,然后又是一层路由守卫,此时路由守卫进入到第二层时,to.path已经不是/home了,这个时候才执行next()放行操作。

正以为如此很多人在使用动态添加路由addRoutes()会遇到下面的情况:
在addRoutes()之后第一次访问被添加的路由会白屏,这是因为刚刚addRoutes()就立刻访问被添加的路由,然而此时addRoutes()没有执行结束,因而找不到刚刚被添加的路由导致白屏。因此需要从新访问一次路由才行。

该如何解决这个问题?
此时就要使用next({ …to, replace: true })来确保addRoutes()时动态添加的路由已经被完全加载上去。
next({ …to, replace: true })中的replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。
因此next({ …to, replace: true })可以写成next({ …to }),不过你应该不希望用户在addRoutes()还没有完成的时候,可以点击浏览器回退按钮搞事情吧。

其实next({ …to })的执行很简单,它会判断:
如果参数to不能找到对应的路由的话,就再执行一次beforeEach((to, from, next)直到其中的next({ …to})能找到对应的路由为止。
也就是说此时addRoutes()已经完成啦,找到对应的路由之后,接下来将执行前往对应路由的beforeEach((to, from, next) ,因此需要用代码来判断这一次是否就是前往对应路由的beforeEach((to, from, next),如果是,就执行next()放行。
如果守卫中没有正确的放行出口的话,会一直next({ …to})进入死循环 !!!
因此你还需要确保在当addRoutes()已经完成时,所执行到的这一次beforeEach((to, from, next)中有一个正确的next()方向出口。

因此想实现动态添加路由的操作的话,代码应该是这样的:

router.beforeEach(async(to, from, next) => {
  NProgress.start()
  document.title = getPageTitle(to.meta.title)
  const hasToken = getToken()
  // 存在 token 说明已经登录
  if (hasToken) {
      // 登录过就不能访问登录界面,需要中断这一次路由守卫,执行下一次路由守卫,并且下一次守卫的to是主页
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      // 保存在store中路由不为空则放行
      if (hasRoles) {
        // 放行
        next()
      } else {
        try {
          const { roles } = await store.dispatch('user/getInfo')
          // 获取并添加动态路由
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          router.addRoutes(accessRoutes)
          // 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
          next({ ...to, replace: true })
        } catch (error) {
          // 异常就清空store,异常提示并执行下一次路由,并且下一次守卫的 to 是登录界面
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  } else {
      // 白名单直接放行
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      // 否则跳转到登录界面
      next(`/login?redirect=${to.path}`)
    }
  }
  NProgress.done()
})

参考:
https://blog.csdn.net/qq_41912398/article/details/109231418

评论

暂无

添加新评论