第7章 路由管理

第20节 编程式导航与路由守卫

7.20.1 编程式导航方法

在Vue Router中,除了使用<router-link>组件进行声明式导航外,我们还可以使用编程式导航来动态控制页面跳转。Vue Router提供了多种编程式导航方法:

基本导航方法

// 1. 字符串路径导航
router.push('/users')

// 2. 对象形式导航
router.push({ path: '/users' })

// 3. 带查询参数的导航
router.push({ path: '/users', query: { page: 1, sort: 'desc' } }) // 结果: /users?page=1&sort=desc

// 4. 命名路由导航
router.push({ name: 'User', params: { id: 123 } }) // 结果: /user/123

// 5. 替换当前记录(不留下历史记录)
router.replace({ path: '/home' })

// 6. 历史记录前进后退
router.go(1)  // 前进一页
router.go(-1) // 后退一页
router.go(3)  // 前进三页
router.go(-3) // 后退三页

组合式API中的使用

在组合式API中,我们可以通过useRouter函数获取路由实例:

import { useRouter } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    
    const goToUser = (userId) => {
      router.push({ name: 'User', params: { id: userId } })
    }
    
    return {
      goToUser
    }
  }
}

选项式API中的使用

在选项式API中,可以通过this.$router访问路由实例:

export default {
  methods: {
    goToUser(userId) {
      this.$router.push({ name: 'User', params: { id: userId } })
    }
  }
}

7.20.2 导航守卫详解

导航守卫是Vue Router提供的一种机制,用于在路由导航过程中进行拦截和控制。它允许我们在路由跳转前、跳转中、跳转后执行特定的逻辑,常用于权限验证、页面标题设置、数据预取等场景。

全局守卫

全局守卫会应用于所有路由导航:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 1. 全局前置守卫 - 路由跳转前执行
router.beforeEach((to, from, next) => {
  // to: 目标路由对象
  // from: 当前路由对象
  // next: 函数,必须调用以继续导航
  
  // 权限验证示例
  const isAuthenticated = localStorage.getItem('token')
  if (to.meta.requiresAuth && !isAuthenticated) {
    // 未登录且需要权限,跳转到登录页
    next('/login')
  } else {
    // 允许导航
    next()
  }
})

// 2. 全局解析守卫 - 路由匹配完成且组件加载前执行
router.beforeResolve(async (to) => {
  // 数据预取示例
  if (to.meta.requiresData) {
    try {
      await to.meta.fetchData()
    } catch (error) {
      // 处理数据预取错误
      console.error('Data fetching failed:', error)
    }
  }
})

// 3. 全局后置钩子 - 路由跳转完成后执行
router.afterEach((to, from) => {
  // 设置页面标题示例
  document.title = to.meta.title || '默认标题'
})

守卫参数详解

  • to: 目标路由对象,包含路由的完整信息
  • from: 当前路由对象,即跳转前的路由
  • next: 函数,用于控制导航流程(仅在beforeEach和beforeRouteEnter中使用)
    • next(): 允许导航继续
    • next(false): 取消导航
    • next('/path')next({ path: '/path' }): 跳转到指定路径
    • next(error): 传递错误,会被全局错误处理器捕获

7.20.3 路由独享守卫与组件内守卫

除了全局守卫外,Vue Router还提供了更细粒度的守卫:

路由独享守卫

路由独享守卫只应用于特定路由,通过路由配置的beforeEnter属性定义:

const routes = [
  {
    path: '/admin',
    component: () => import('../views/Admin.vue'),
    meta: { requiresAuth: true },
    beforeEnter: (to, from, next) => {
      // 仅对/admin路由生效的守卫逻辑
      const isAdmin = localStorage.getItem('role') === 'admin'
      if (isAdmin) {
        next()
      } else {
        next('/403') // 无权限跳转到403页面
      }
    }
  }
]

组件内守卫

组件内守卫允许在组件内部定义路由守卫,主要有以下几种:

export default {
  // 1. 进入组件前调用
  beforeRouteEnter(to, from, next) {
    // 注意:此时组件实例还未创建,无法访问this
    next(vm => {
      // vm是组件实例,可以访问this
      vm.initData()
    })
  },
  
  // 2. 组件复用时调用(同一组件,不同路由参数)
  beforeRouteUpdate(to, from, next) {
    // 可以访问this
    this.updateData(to.params.id)
    next()
  },
  
  // 3. 离开组件前调用
  beforeRouteLeave(to, from, next) {
    // 可以访问this
    if (this.hasUnsavedChanges) {
      if (confirm('您有未保存的更改,确定要离开吗?')) {
        next()
      } else {
        next(false) // 取消导航
      }
    } else {
      next()
    }
  }
}

组合式API中的组件内守卫

在组合式API中,我们可以使用onBeforeRouteUpdateonBeforeRouteLeave钩子:

import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'

export default {
  setup() {
    // 组件复用时调用
    onBeforeRouteUpdate((to, from) => {
      console.log('Route updated:', to.params.id)
    })
    
    // 离开组件前调用
    onBeforeRouteLeave((to, from, next) => {
      const hasUnsavedChanges = true // 示例逻辑
      if (hasUnsavedChanges) {
        if (confirm('您有未保存的更改,确定要离开吗?')) {
          next()
        } else {
          next(false)
        }
      } else {
        next()
      }
    })
  }
}

7.20.4 导航故障处理

在路由导航过程中,可能会遇到各种故障,如取消导航、重定向、权限不足等。Vue Router 4提供了导航故障处理机制,让我们能够捕获和处理这些故障。

捕获导航故障

try {
  const result = await router.push('/protected-route')
  if (result) {
    // 导航成功
    console.log('Navigation succeeded:', result)
  }
} catch (error) {
  // 导航失败
  console.error('Navigation failed:', error)
}

导航结果类型

导航结果可能是以下几种情况:

// 1. 导航成功
const successResult = {
  fullPath: '/success',
  // ... 其他路由信息
}

// 2. 导航被取消
const canceledResult = {
  fullPath: '/canceled',
  redirectedFrom: null,
  // ...
}

// 3. 导航被重定向
const redirectedResult = {
  fullPath: '/redirected',
  redirectedFrom: {
    fullPath: '/original',
    // ...
  },
  // ...
}

使用isNavigationFailure判断故障类型

Vue Router提供了isNavigationFailure函数来判断导航是否失败,以及失败的具体类型:

import { isNavigationFailure, NavigationFailureType } from 'vue-router'

try {
  await router.push('/protected-route')
} catch (error) {
  if (isNavigationFailure(error, NavigationFailureType.canceled)) {
    console.log('导航被取消')
  } else if (isNavigationFailure(error, NavigationFailureType.aborted)) {
    console.log('导航被终止')
  } else if (isNavigationFailure(error, NavigationFailureType.duplicated)) {
    console.log('导航重复')
  } else if (isNavigationFailure(error, NavigationFailureType.redirected)) {
    console.log('导航被重定向')
  }
}

最佳实践与注意事项

  1. 守卫执行顺序

    • 全局前置守卫 → 路由独享守卫 → 组件内守卫(beforeRouteEnter) → 全局解析守卫 → 导航完成 → 全局后置钩子
  2. 避免无限重定向

    // 错误示例:会导致无限重定向
    router.beforeEach((to, from, next) => {
      if (!isAuthenticated) {
        next('/login') // 如果/login也需要认证,就会无限重定向
      }
    })
    
    // 正确示例:排除登录页
    router.beforeEach((to, from, next) => {
      if (to.meta.requiresAuth && !isAuthenticated) {
        next('/login')
      } else {
        next()
      }
    })
  3. 数据预取策略

    • 在全局解析守卫中进行数据预取,确保组件渲染前数据已准备就绪
    • 或在组件内使用async setup结合onMounted进行数据加载
  4. 导航守卫中的异步操作

    • 确保在异步操作完成后调用next()
    • 推荐使用async/await语法

小结

本节我们学习了Vue Router中的编程式导航与路由守卫,包括:

  • 编程式导航的多种方法:pushreplacego
  • 全局守卫、路由独享守卫和组件内守卫的使用
  • 导航故障的捕获和处理
  • 导航守卫的最佳实践

通过合理使用这些功能,我们可以实现复杂的导航逻辑、权限控制和数据预取,从而构建更加健壮和用户友好的单页应用。

思考与练习

  1. 编程式导航和声明式导航有什么区别?各自适用什么场景?
  2. 实现一个需要登录才能访问的后台管理系统,使用全局前置守卫进行权限验证。
  3. 在组件内守卫中实现表单未保存时的离开确认功能。
  4. 尝试捕获并处理不同类型的导航故障。
« 上一篇 路由配置进阶 下一篇 » 20-advanced-router-features