Vue Router4 路由守卫优化:放弃next(),通过返回对象简化代码

148 阅读5分钟

Vue Router4 路由守卫优化:放弃next(),通过返回对象简化代码

在 Vue.js 项目中,vue-router 是我们进行路由管理的重要工具。在一些场景下,我们需要在路由跳转前做一些权限控制、用户验证或动态加载路由等操作,这时我们常常使用 beforeEach 守卫来进行处理。但在复杂的逻辑中,守卫的代码可能会变得冗长和复杂。如何让路由守卫的代码更简洁、清晰并且易于维护呢?

今天,我们将探讨如何通过返回路由对象来优化 Vue Router 路由守卫的代码,使得逻辑更加清晰,减少不必要的代码冗余。

原始问题:路由守卫中的复杂逻辑

假设我们在 Vue 项目中有一个需求:用户需要先登录才能访问特定的路由页面。如果用户未登录,则需要跳转到登录页面。登录后,用户可以访问他们有权限的路由页面。并且,在某些情况下,我们还需要动态添加路由并根据角色来控制访问权限。

如果我们将这些逻辑写在 beforeEach 守卫中,代码通常会变得比较复杂。例如:

  • 登录状态的判断;
  • 白名单页面的检查;
  • 动态加载用户角色信息;
  • 添加动态路由等。

这时,如果每个条件都使用 next()router.replace() 来控制跳转,代码的可读性和维护性会变得比较差。

我们来看示例代码片段导航守卫 | Vue Router

可选的第三个参数 next

来自导航守卫 | Vue Router

在之前的 Vue Router 版本中,还可以使用 第三个参数 next 。这是一个常见的错误来源,我们经过 RFC 讨论将其移除。然而,它仍然是被支持的,这意味着你可以向任何导航守卫传递第三个参数。在这种情况下,确保 next 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到/login错误用例

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

或者

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) router.replace('/login')
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) router.replace('/login')
  else next()
})

要避免通过 next() 来控制跳转,或者希望以某种方式返回路由的目标信息。

优化方案:通过返回路由对象简化逻辑

我们可以通过 return { name: 'Login' }return { name: 'Home' } 的方式来简化守卫逻辑。直接返回路由对象,vue-router 会自动进行跳转,这样可以减少冗余的 next() 调用,避免嵌套的逻辑。

以下是优化后的代码:

import { useRouter } from 'vue-router'
const router = useRouter()
router.beforeEach(async (to, from) => {
  if (getToken()) {
    // 已登录的情况下,处理登录重定向逻辑
    if (to.path === '/login') {
      return { name: 'Home' }  // 登录页不需要访问,直接重定向到首页
    }
    if (whiteList.includes(to.path)) {
      NProgress.done()
      return true  // 白名单页面直接放行
    }
    if (useUserStore().roles.length === 0) {
      isRelogin.show = true
      try {
        // 拉取用户信息并生成路由
        await useUserStore().getInfo()
        const accessRoutes = await usePermissionStore().generateRoutes()
        // 动态添加可访问的路由
        accessRoutes.forEach(route => {
          if (!isHttp(route.path)) {
            router.addRoute(route)
          }
        })
        return { ...to, replace: true }  // 完成路由添加后跳转
      } catch (err) {
        useUserStore().logOut()
        return { name: 'Home' }  // 如果错误,重定向到首页
      }
    }
    // 如果用户已加载 roles 信息,直接放行
    NProgress.done()
    return true  // 允许访问
  } else {
    // 没有 token 时处理
    if (whiteList.includes(to.path)) {
      return true  // 白名单页面直接放行
    }
    return { name: 'Login', query: { redirect: to.fullPath } }  // 重定向到登录页
  }
})

优化要点:

  1. 返回路由对象

    • 通过 return { name: 'Login' }return { name: 'Home' } 等对象,可以让 vue-router 自动进行跳转,而不需要显式调用 next()router.replace() 等方法。
  2. 简化逻辑判断

    • 我们通过判断登录状态、白名单、角色信息等,直接决定跳转的目标路径。避免了多层嵌套和冗余的 next() 调用,使得代码更加直观和简洁。
  3. 避免多余的路由跳转

    • 在用户已经登录并且有权限时,直接通过 return true 放行当前路由。无需再通过复杂的路由跳转来实现目标。
  4. 动态路由添加后的跳转

    • 当用户的角色信息尚未加载时,我们通过动态生成路由并在完成后跳转。通过 return { ...to, replace: true } 让 Vue Router 跳转到用户请求的目标页面,避免了多余的手动控制。

优点:

  • 可读性更强:通过返回对象控制跳转,代码更简洁、逻辑清晰,容易理解。
  • 减少冗余:避免了多余的 next()router.replace(),使得路由守卫更加精简。
  • 易于维护:在复杂的条件下,逻辑清晰的 return 语句更易于修改和维护。

结语

通过返回路由对象来控制 Vue Router 的跳转,不仅能让代码更简洁,还能提高可读性和维护性。特别是在涉及到登录验证、权限管理、动态路由等复杂逻辑时,这种优化方法能够使代码更加清晰,并减少不必要的冗余部分。希望这篇文章能帮助你优化 Vue 项目中的路由守卫逻辑,让你的代码更加高效与易懂。