Vue Router 路由管理:导航守卫与动态路由

FreeGuideOnline 最新 2026-06-15

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) { /* ... */ }
}

完整守卫解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave
  3. 调用全局 beforeEach
  4. 在重用的组件里调用 beforeRouteUpdate
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局 beforeResolve(Vue Router 4 新增)。
  9. 导航被确认。
  10. 调用全局 afterEach
  11. 触发 DOM 更新。
  12. 调用 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('/')
  • 动态路由参数变化但组件不更新:记得使用 watchonBeforeRouteUpdate
  • router.addRoute 后路由未生效:对于动态添加的主要路由,可能需用 router.replace 触发一次导航才能匹配到。另外,如果已经在该路由路径上,可使用 router.replace(router.currentRoute) 来强制刷新视图。
  • 懒加载导致白屏:检查网络,并为加载失败的情况增加错误处理(如 component: () => import(...).catch(/* 后备组件 */))。

小结

  • 导航守卫 提供了从全局到组件级别的控制能力,合理利用可以构造出可靠的权限体系和交互体验。
  • 动态路由 通过参数化、嵌套、懒加载等手段,让单页面应用具备灵活且高性能的页面组织方式。
  • 将两者组合使用,你便可以构建出健壮、用户体验良好的 Vue.js 前端项目。

现在,尝试在你自己的项目中运用这些技巧吧!结合 Vue Devtools 的 Router 面板可以直观地观察路由变化和守卫执行过程。