Vue Router 路由管理:导航守卫与动态路由
Vue Router 路由管理:导航守卫与动态路由
Vue Router 是 Vue.js 官方的路由管理器,它和 Vue.js 深度集成,让构建单页面应用(SPA)变得轻而易举。本教程将重点讲解两大核心功能:导航守卫(控制访问权限与跳转逻辑)和动态路由(匹配不同参数并渲染对应视图)。我们将从基础概念出发,配合可运行的示例,带你逐步掌握企业级路由管理。
环境准备
请确保已创建 Vue 3 项目并安装了 Vue Router 4:
npm create vue@latest
# 选择添加 Vue Router
# 或手动安装:npm install vue-router@4
导航守卫:控制路由跳转的“安检员”
导航守卫主要用于在路由跳转前、跳转后或跳转过程中执行逻辑,例如权限验证、页面标题修改、数据预加载等。Vue Router 提供三种守卫级别:全局守卫、路由独享守卫和组件内守卫。
1. 全局前置守卫 beforeEach
项目中最常用的守卫,任何路由跳转都会触发它。我们通常在入口文件 router/index.js 中定义。
const router = createRouter({ ... })
router.beforeEach((to, from, next) => {
// to: 即将进入的目标路由对象
// from: 当前正要离开的路由对象
// next: 必须调用此函数来 resolve 这次导航
const isAuthenticated = checkAuth() // 伪代码:检查是否登录
if (to.meta.requiresAuth && !isAuthenticated) {
// 如果目标路由需要认证且用户未登录,重定向到登录页
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
next() // 正常放行
}
})
next()不再传递参数表示允许导航next(false)中止本次导航next('/')或next({ path: '/' })重定向到其他地址
使用场景:登录鉴权、动态设置网页标题(document.title = to.meta.title)、页面切换时的进度条控制等。
2. 全局后置守卫 afterEach
后置守卫不接受 next 函数,因为此时导航已经确认。它常用于页面访问统计或关闭加载动画。
router.afterEach((to, from) => {
// 发送页面浏览统计
analytics.pageView(to.fullPath)
})
3. 路由独享守卫 beforeEnter
在单个路由配置中直接定义,只对当前路由生效。可以写多个,按顺序执行。
const routes = [
{
path: '/dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
// 直接在此处校验权限或重定向
if (!userHasAdminRole()) {
next({ name: 'NotFound' })
} else {
next()
}
}
}
]
它和全局前置守卫的参数完全一致,但作用域更小,适合特定页面的准入控制。
4. 组件内守卫
在路由组件内部直接定义三个生命周期钩子:
beforeRouteEnter(to, from, next):在路由进入该组件前调用,此时组件实例还未创建,所以this不可用。beforeRouteUpdate(to, from, next):在当前组件被复用时调用(如动态路由/user/:id从 1 切到 2),可以获取更新后的参数。beforeRouteLeave(to, from, next):离开该组件时调用,常用于未保存表单提醒。
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 组合式 API 写法
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('确定离开吗?未保存的数据将丢失')
if (!answer) return false
})
onBeforeRouteUpdate((to, from) => {
// 获取新参数,重新请求数据
fetchUserData(to.params.id)
})
</script>
在选项式 API 中,直接作为选项定义:
export default {
beforeRouteLeave(to, from, next) { /* ... */ }
}
完整守卫解析流程:
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave。 - 调用全局
beforeEach。 - 在重用的组件里调用
beforeRouteUpdate。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局
beforeResolve(Vue Router 4 新增)。 - 导航被确认。
- 调用全局
afterEach。 - 触发 DOM 更新。
- 调用
beforeRouteEnter中传给next的回调函数,此时组件实例可用。
动态路由:让路由活起来
动态路由允许我们在路径中使用占位符(参数),匹配不同的值并渲染同一组件。这是实现用户详情页、文章页等场景的核心。
1. 动态路径参数
使用冒号 : 标记参数。
const routes = [
{ path: '/user/:id', component: User },
{ path: '/product/:category/:id', component: Product } // 多段参数
]
在组件中通过 $route.params 访问:
<template>
<div>当前用户 ID:{{ $route.params.id }}</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // 字符串类型,注意转换
</script>
2. 响应路由参数变化
当导航到 /user/1 再到 /user/2 时,相同的 User 组件会被复用。此时组件的生命周期钩子不会重新执行,你需要手动侦听参数变化。
方法一:使用 watch
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
(newId) => {
fetchData(newId)
}
)
方法二:使用 beforeRouteUpdate 守卫(上文已介绍),推荐在需要组合多个参数或复杂逻辑时使用。
3. 动态路由参数的匹配模式
Vue Router 使用 path-to-regexp 库进行路径匹配,你可以定义可选参数(?)、星号通配符等。
{ path: '/search/:keyword?', component: Search } // 可选参数,匹配 /search 和 /search/vue
{ path: '/user-:afterUser(.*)', component: UserWildcard } // 自定义正则:捕获 user- 后所有内容
常用场景:搜索引擎友好 URL 或需要灵活路段处理的情况。
其中,(.*) 表示匹配任意字符(包括 /),这样 /user-admin/profile 也能捕获到。
4. 嵌套路由与动态段
动态路由常与嵌套路由结合,构建复杂布局。
const routes = [
{
path: '/user/:id',
component: UserLayout,
children: [
{
path: '', // 默认子路由
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
]
此时,/user/42 显示用户资料,/user/42/posts 显示其文章列表。在 UserLayout 组件中必须使用 <router-view> 来渲染子组件。
5. 动态添加路由
有时后端接口会返回路由配置,或者需要按权限动态注入路由。使用 router.addRoute()。
// 添加平级路由
router.addRoute({ path: '/new-page', component: NewPage })
// 添加为嵌套子路由
router.addRoute('user', { path: 'settings', component: UserSettings })
注意:不要重复动态添加同名的路由,否则会覆盖或造成冲突。可以在使用前检查是否已存在:
if (!router.hasRoute('AdminDashboard')) {
router.addRoute(adminRoute)
}
6. 路由懒加载与动态导入
为了让首屏更快,建议所有路由组件都使用动态导入。
// 静态导入
// import UserProfile from './views/UserProfile.vue'
// 动态导入(懒加载)
const UserProfile = () => import('./views/UserProfile.vue')
const routes = [
{
path: '/profile/:id',
component: UserProfile,
meta: { requiresAuth: true }
}
]
Webpack / Vite 会将动态导入的组件拆分为独立的代码块(chunk),只在路由被访问时才下载。你还可以在导入时添加魔法注释,自定义 chunk 名称:
() => import(/* webpackChunkName: "user-group" */ './views/UserProfile.vue')
实战演练:一个需要登录的动态文章系统
让我们把上述知识串起来,实现一个迷你博客系统。
路由配置 (router/index.js)
import { createRouter, createWebHistory } from 'vue-router'
const Article = () => import('@/views/Article.vue')
const Login = () => import('@/views/Login.vue')
const NotFound = () => import('@/views/NotFound.vue')
const routes = [
{
path: '/login',
name: 'Login',
component: Login,
meta: { title: '登录' }
},
{
path: '/article/:id',
name: 'Article',
component: Article,
meta: { requiresAuth: true, title: '文章详情' }
},
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫:鉴权 + 标题
router.beforeEach((to, from, next) => {
const isLoggedIn = localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
document.title = to.meta.title || '默认标题'
next()
}
})
export default router
文章组件 Article.vue
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const article = ref(null)
async function fetchArticle(id) {
const res = await fetch(`/api/articles/${id}`)
article.value = await res.json()
}
fetchArticle(route.params.id)
watch(() => route.params.id, (newId) => {
fetchArticle(newId)
})
</script>
<template>
<div v-if="article">
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
</div>
<div v-else>加载中...</div>
</template>
当用户未登录访问 /article/123 时,会被重定向到 /login?redirect=/article/123。登录成功后可以用 router.push({ path: redirect }) 跳回。
常见问题与排查建议
- 导航守卫死循环:在
beforeEach中,务必确保当用户已登录时不再次触发重定向到登录页,避免next({ name: 'Login' })又被守卫拦截并继续跳转。可增加判断if (to.name === 'Login' && isLoggedIn) next('/')。 - 动态路由参数变化但组件不更新:记得使用
watch或onBeforeRouteUpdate。 router.addRoute后路由未生效:对于动态添加的主要路由,可能需用router.replace触发一次导航才能匹配到。另外,如果已经在该路由路径上,可使用router.replace(router.currentRoute)来强制刷新视图。- 懒加载导致白屏:检查网络,并为加载失败的情况增加错误处理(如
component: () => import(...).catch(/* 后备组件 */))。
小结
- 导航守卫 提供了从全局到组件级别的控制能力,合理利用可以构造出可靠的权限体系和交互体验。
- 动态路由 通过参数化、嵌套、懒加载等手段,让单页面应用具备灵活且高性能的页面组织方式。
- 将两者组合使用,你便可以构建出健壮、用户体验良好的 Vue.js 前端项目。
现在,尝试在你自己的项目中运用这些技巧吧!结合 Vue Devtools 的 Router 面板可以直观地观察路由变化和守卫执行过程。