Skip to content

导航守卫 | Vue Router

概述

导航守卫是 Vue Router 的核心功能之一,用于控制路由导航。它们允许你在路由切换的不同阶段执行逻辑,如权限验证、数据预加载、页面访问控制等。

守卫类型

Vue Router 提供了三种主要类型的导航守卫:

  1. 全局守卫 - 应用于所有路由
  2. 路由独享守卫 - 应用于特定路由
  3. 组件内守卫 - 在组件内部使用

全局守卫

beforeEach - 全局前置守卫

在路由导航开始前触发,适合进行权限验证。

javascript
const router = createRouter({ ... })

router.beforeEach((to, from, next) => {
  // 检查是否需要认证
  if (to.meta.requiresAuth && !isAuthenticated()) {
    // 重定向到登录页
    next('/login')
  } else {
    // 继续导航
    next()
  }
})

beforeResolve - 全局解析守卫

在所有组件内守卫和异步路由组件被解析之后触发。

javascript
router.beforeResolve(async (to, from) => {
  // 数据预加载
  if (to.meta.requiresData) {
    await preloadRequiredData(to)
  }
})

afterEach - 全局后置钩子

在导航完成后触发,适合进行日志记录、页面统计等。

javascript
router.afterEach((to, from) => {
  // 页面访问统计
  analytics.trackPageView(to.fullPath)
  
  // 更新页面标题
  if (to.meta.title) {
    document.title = `${to.meta.title} - 我的应用`
  }
})

路由独享守卫

beforeEnter

在路由配置中定义,只对特定路由生效。

javascript
const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: (to, from, next) => {
      if (!isAdmin()) {
        next('/access-denied')
      } else {
        next()
      }
    }
  }
]

组件内守卫

选项式 API

javascript
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被验证前调用
    next(vm => {
      // 通过 `vm` 访问组件实例
    })
  },
  
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    next()
  },
  
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    next()
  }
}

组合式 API

vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 路由离开守卫
onBeforeRouteLeave((to, from, next) => {
  if (hasUnsavedChanges.value) {
    const answer = confirm('有未保存的更改,确定要离开吗?')
    if (answer) {
      next()
    } else {
      next(false)
    }
  } else {
    next()
  }
})

// 路由更新守卫
onBeforeRouteUpdate(async (to, from, next) => {
  if (to.params.id !== from.params.id) {
    await loadUserData(to.params.id)
  }
  next()
})
</script>

实际应用场景

场景 1:用户认证系统

javascript
// 认证守卫
router.beforeEach((to, from, next) => {
  const isAuthRequired = to.meta.requiresAuth
  const isAuthenticated = checkAuthStatus()
  
  if (isAuthRequired && !isAuthenticated) {
    // 保存目标路由,登录后重定向
    next({
      path: '/login',
      query: { redirect: to.fullPath }
    })
  } else if (to.path === '/login' && isAuthenticated) {
    // 已登录用户访问登录页,重定向到首页
    next('/')
  } else {
    next()
  }
})

// 登录组件
const Login = {
  methods: {
    async login() {
      await auth.login(this.credentials)
      const redirect = this.$route.query.redirect || '/'
      this.$router.push(redirect)
    }
  }
}

场景 2:权限管理系统

javascript
// 权限验证守卫
router.beforeEach((to, from, next) => {
  const requiredPermissions = to.meta.permissions || []
  
  if (requiredPermissions.length > 0) {
    const userPermissions = getUserPermissions()
    const hasPermission = requiredPermissions.every(perm => 
      userPermissions.includes(perm)
    )
    
    if (!hasPermission) {
      next('/forbidden')
      return
    }
  }
  
  next()
})

// 路由配置
const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    meta: { 
      permissions: ['admin.access', 'user.manage'] 
    },
    children: [
      {
        path: 'users',
        component: UserManagement,
        meta: { permissions: ['user.manage'] }
      }
    ]
  }
]

场景 3:多步骤表单导航

javascript
// 表单步骤守卫
const formRoutes = [
  {
    path: '/form/step1',
    component: Step1,
    meta: { formStep: 1 }
  },
  {
    path: '/form/step2', 
    component: Step2,
    meta: { formStep: 2 },
    beforeEnter: (to, from, next) => {
      // 检查上一步是否完成
      if (!formStore.isStepCompleted(1)) {
        next('/form/step1')
      } else {
        next()
      }
    }
  }
]

// 表单离开守卫
const FormComponent = {
  beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges && !this.isSubmitting) {
      const answer = confirm('表单数据尚未保存,确定要离开吗?')
      next(answer)
    } else {
      next()
    }
  }
}

高级用法

异步守卫链

javascript
// 异步守卫处理
async function executeGuardChain(to, from, guards) {
  for (const guard of guards) {
    const result = await guard(to, from)
    if (result !== undefined) {
      return result
    }
  }
  return true
}

router.beforeEach(async (to, from, next) => {
  const guards = [
    authGuard,
    permissionGuard,
    featureFlagGuard,
    maintenanceGuard
  ]
  
  const result = await executeGuardChain(to, from, guards)
  
  if (result === true) {
    next()
  } else if (typeof result === 'string' || typeof result === 'object') {
    next(result)
  } else {
    next(false)
  }
})

守卫元编程

javascript
// 动态守卫注册系统
class GuardManager {
  constructor(router) {
    this.router = router
    this.guards = new Map()
  }
  
  registerGuard(name, guard) {
    this.guards.set(name, guard)
    this.updateGlobalGuard()
  }
  
  unregisterGuard(name) {
    this.guards.delete(name)
    this.updateGlobalGuard()
  }
  
  updateGlobalGuard() {
    // 移除旧的全局守卫
    this.router.beforeEach(() => {})
    
    // 注册新的组合守卫
    this.router.beforeEach(async (to, from, next) => {
      for (const [name, guard] of this.guards) {
        try {
          const result = await guard(to, from)
          if (result !== undefined) {
            next(result)
            return
          }
        } catch (error) {
          console.error(`守卫 ${name} 执行失败:`, error)
          next(false)
          return
        }
      }
      next()
    })
  }
}

最佳实践

1. 守卫组织策略

javascript
// 按功能模块组织守卫
const authGuards = [
  requireAuth,
  checkSession,
  validateToken
]

const businessGuards = [
  checkSubscription,
  validatePermissions,
  enforceRateLimit
]

// 组合守卫
router.beforeEach(async (to, from, next) => {
  const guards = [...authGuards, ...businessGuards]
  
  for (const guard of guards) {
    const result = await guard(to, from)
    if (result) {
      next(result)
      return
    }
  }
  
  next()
})

2. 错误处理

javascript
// 守卫错误处理
router.beforeEach(async (to, from, next) => {
  try {
    // 执行守卫逻辑
    await executeGuards(to, from)
    next()
  } catch (error) {
    console.error('导航守卫执行失败:', error)
    
    // 根据错误类型处理
    if (error instanceof AuthenticationError) {
      next('/login')
    } else if (error instanceof PermissionError) {
      next('/forbidden')
    } else {
      next('/error')
    }
  }
})

3. 性能优化

javascript
// 守卫缓存和优化
const guardCache = new Map()

function createCachedGuard(guard) {
  return async (to, from) => {
    const cacheKey = `${to.fullPath}-${from.fullPath}`
    
    if (guardCache.has(cacheKey)) {
      return guardCache.get(cacheKey)
    }
    
    const result = await guard(to, from)
    guardCache.set(cacheKey, result)
    
    // 设置缓存过期时间
    setTimeout(() => {
      guardCache.delete(cacheKey)
    }, 5000)
    
    return result
  }
}

注意事项

  1. 守卫顺序 - 理解不同守卫的执行顺序
  2. 异步处理 - 正确处理异步守卫和 Promise
  3. 性能影响 - 避免在守卫中执行昂贵操作
  4. 错误边界 - 为守卫添加适当的错误处理

🛡️ 总结:导航守卫是 Vue Router 的强大功能,合理使用可以构建出安全、可靠的单页应用程序。

🚀 Vue Router - 让前端路由变得简单而强大 | 构建现代化单页应用的最佳选择