第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中,我们可以使用onBeforeRouteUpdate和onBeforeRouteLeave钩子:
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('导航被重定向')
}
}最佳实践与注意事项
守卫执行顺序:
- 全局前置守卫 → 路由独享守卫 → 组件内守卫(beforeRouteEnter) → 全局解析守卫 → 导航完成 → 全局后置钩子
避免无限重定向:
// 错误示例:会导致无限重定向 router.beforeEach((to, from, next) => { if (!isAuthenticated) { next('/login') // 如果/login也需要认证,就会无限重定向 } }) // 正确示例:排除登录页 router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isAuthenticated) { next('/login') } else { next() } })数据预取策略:
- 在全局解析守卫中进行数据预取,确保组件渲染前数据已准备就绪
- 或在组件内使用
async setup结合onMounted进行数据加载
导航守卫中的异步操作:
- 确保在异步操作完成后调用
next() - 推荐使用
async/await语法
- 确保在异步操作完成后调用
小结
本节我们学习了Vue Router中的编程式导航与路由守卫,包括:
- 编程式导航的多种方法:
push、replace、go等 - 全局守卫、路由独享守卫和组件内守卫的使用
- 导航故障的捕获和处理
- 导航守卫的最佳实践
通过合理使用这些功能,我们可以实现复杂的导航逻辑、权限控制和数据预取,从而构建更加健壮和用户友好的单页应用。
思考与练习
- 编程式导航和声明式导航有什么区别?各自适用什么场景?
- 实现一个需要登录才能访问的后台管理系统,使用全局前置守卫进行权限验证。
- 在组件内守卫中实现表单未保存时的离开确认功能。
- 尝试捕获并处理不同类型的导航故障。