«
VUE 路由守卫

时间:2022-4   


最近因为 next() 遇到了不少问题,在这里记录一下

首先是路由守卫,是不是感觉简简单单

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

但是在看别的项目时常常能看到 next('/logon') 、 next(to) 或者 next({ ...to, replace: true })  这又是啥意思呢

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

可以这么理解:

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

而对于上面说的中断当前导航,执行新的导航打个比方:

现在我有一个守卫,在守卫中我使用 next('/logon') ,肯定有同学认为是会直接跳转到 /logon 路由:

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

然而年轻人不讲武德,执行时需要这么看:

beforeEach((to, from, next) => {
  beforeEach(('/logon', 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()
   }
}

我本来要去 /home 路由,因此执行了第一次  beforeEach((to, from, next)

但是这个路由守卫中判断了如果要去的地方是 '/home' ,就执行 next('/logon')

所以想要访问 /home 可以这么看

beforeEach((to, from, next) => {
   beforeEach(('/logon', from, next) => {
     next()  // 现在要去的地方不是 /home , 因此放行
   }
}

注意:重点就在这, next('/logon') 不是说直接去 /logon 路由,而是中断(不是CPU的那个中断!VUE中的中断就是此时不会执行 router.afterEach(() => {} )这一次路由守卫的操作,又进入一次路由守卫,就像嵌套一样,一层路由守卫,然后又是一层路由守卫,此时路由守卫进入到第二层时, 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((to, from, next) => {
 const token = sessionStorage.getItem('access_token')
 // 存在 token 说明已经登录
 if (token) {
   // 登录过就不能访问登录界面,需要中断这一次路由守卫,执行下一次路由守卫,并且下一次守卫的to是主页'
   if (to.path === '/login') {
     next({ path: '/' })
   }
   // 保存在store中路由不为空则放行 (如果执行了刷新操作,则 store 里的路由为空,此时需要重新添加路由)
   if (store.getters.getRoutes.length || to.name != null) {
     //放行
     next()
   } else {
     // 将路由添加到 store 中,用来标记已添加动态路由
     store.commit('ADD_ROUTER', '需要添加的路由')
     router.addRoutes('需要添加的路由')
     // 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
     next({ ...to, replace: true })
   }
 } else {
   // 未登录时,注意 :在这里也许你的项目不只有 logon 不需要登录 ,register 等其他不需要登录的页面也需要处理
   if (to.path !== '/logon') {
     next({ path: '/logon' })
   } else {
     next()
   }
 }

万一还是听不懂,也可以 next() 比作 Java Filter 里的 chain.doFilter(req, resp)

@WebFilter(filterName = "EncodingFilter", urlPatterns = "/*")
public class EncodingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        // 设置编码
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");
        chain.doFilter(req, resp);  // next()
    }

}