前端框架

Vue Router 4 深度指南:从基础到高级路由方案

深入学习 Vue Router 4 的路由配置、导航守卫、懒加载、动态路由等核心概念,打造高效的单页应用

15 分钟阅读
#Vue Router 4 #SPA #路由管理 #导航守卫

📖 文章概述

Vue Router 4 是 Vue 3 官方路由管理库,提供了强大而灵活的路由解决方案。本文深入讲解其核心特性、最佳实践和常见模式。


🎯 核心概念速览

什么是路由?

路由是 SPA(单页应用)的核心,用于管理应用的不同视图状态和导航流程。

// 传统多页应用:每个页面是独立的 HTML 文件
// index.html → /about.html → /contact.html

// SPA 应用:单个 HTML,通过路由切换组件
// / → /about → /contact (同一个 HTML,不同组件)

Vue Router 4 的三大改进

特性Vue Router 3Vue Router 4
Vue 版本Vue 2Vue 3+
大小~64KB~35KB
TypeScript部分支持完整支持
组合式 API
性能良好更好

🚀 快速开始

1. 安装和基础配置

npm install vue-router@4

创建路由配置文件

// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

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

export default router

在 Vue 应用中使用

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

应用模板

<!-- App.vue -->
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/about">About</router-link>
    </nav>
    
    <!-- 渲染当前路由组件 -->
    <router-view />
  </div>
</template>

🔀 高级路由配置

2. 动态路由参数

// 配置动态路由
{
  path: '/user/:id',
  name: 'UserProfile',
  component: UserProfile
}

// 访问参数
import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    
    // 获取路由参数
    const userId = route.params.id
    const query = route.query.tab  // 查询字符串
    
    return { userId, query }
  }
}

3. 嵌套路由(子路由)

{
  path: '/dashboard',
  component: Dashboard,
  children: [
    {
      path: 'profile',      // 完整路径:/dashboard/profile
      component: Profile
    },
    {
      path: 'settings',     // 完整路径:/dashboard/settings
      component: Settings
    },
    {
      path: '',             // 完整路径:/dashboard
      component: DashboardHome
    }
  ]
}

渲染嵌套组件:

<!-- Dashboard.vue -->
<template>
  <div class="dashboard">
    <aside>
      <nav>
        <router-link to="/dashboard">Home</router-link>
        <router-link to="/dashboard/profile">Profile</router-link>
        <router-link to="/dashboard/settings">Settings</router-link>
      </nav>
    </aside>
    
    <main>
      <!-- 渲染子路由组件 -->
      <router-view />
    </main>
  </div>
</template>

4. 命名视图(多个 router-view)

{
  path: '/layout',
  component: Layout,
  children: [
    {
      path: 'dashboard',
      components: {
        default: Dashboard,
        sidebar: Sidebar,
        header: Header
      }
    }
  ]
}
<!-- Layout.vue -->
<template>
  <div class="layout">
    <router-view name="header" />
    <router-view name="sidebar" />
    <router-view />  <!-- default view -->
  </div>
</template>

5. 路由懒加载

// 方式 1:使用 ES6 动态导入
{
  path: '/analytics',
  component: () => import('../pages/Analytics.vue')
}

// 方式 2:带注释的 webpack 分割
{
  path: '/report',
  component: () => import(/* webpackChunkName: "report" */ '../pages/Report.vue')
}

// 方式 3:带加载状态
import { defineAsyncComponent } from 'vue'

{
  path: '/admin',
  component: defineAsyncComponent({
    loader: () => import('../pages/Admin.vue'),
    loadingComponent: LoadingSpinner,
    delay: 200,
    timeout: 10000,
    errorComponent: ErrorComponent,
    onError: (error, retry, fail, attempts) => {
      if (attempts <= 3) {
        setTimeout(() => retry(), 1000)
      } else {
        fail()
      }
    }
  })
}

🛡️ 导航守卫(Navigation Guards)

导航守卫是路由跳转时的拦截钩子,用于权限验证、页面跳转前处理等。

6. 全局守卫

// 全局前置守卫
router.beforeEach((to, from, next) => {
  console.log(`从 ${from.path} 导航到 ${to.path}`)
  
  // 权限检查
  if (to.meta.requiresAuth) {
    const isAuthenticated = !!localStorage.getItem('token')
    if (isAuthenticated) {
      next()
    } else {
      next('/login')  // 重定向到登录页
    }
  } else {
    next()
  }
})

// 全局解析守卫(在所有守卫和异步路由组件完成后调用)
router.beforeResolve(async (to, from) => {
  // 预加载数据
  if (to.meta.requiresData) {
    try {
      await fetchData(to.params.id)
    } catch (error) {
      return false  // 取消导航
    }
  }
})

// 全局后置钩子(不能改变导航结果)
router.afterEach((to, from) => {
  // 更新页面标题
  document.title = to.meta.title || 'Default Title'
  
  // 页面统计
  trackPageView(to.path)
})

7. 路由级别守卫

{
  path: '/admin',
  component: Admin,
  meta: { 
    requiresAuth: true,
    title: 'Admin Panel'
  },
  beforeEnter: (to, from, next) => {
    // 仅在进入此路由时执行
    const isAdmin = checkAdminPermission()
    if (isAdmin) {
      next()
    } else {
      next('/unauthorized')
    }
  }
}

8. 组件级别守卫

<script setup lang="ts">
import { onBeforeRouteUpdate, useRoute } from 'vue-router'

// 在当前组件内导航时调用
onBeforeRouteUpdate(async (to, from) => {
  if (to.params.id !== from.params.id) {
    // 重新加载数据
    const data = await fetchUserData(to.params.id)
    // 处理数据...
  }
})

// 离开此路由时调用
onBeforeRouteLeave((to, from) => {
  if (unsavedChanges.value) {
    return confirm('有未保存的更改,确定离开吗?')
  }
})
</script>

📊 实战:完整身份验证示例

// src/router/guards.ts
import { Router } from 'vue-router'
import { useAuthStore } from '../stores/auth'

export function setupAuthGuards(router: Router) {
  router.beforeEach(async (to, from, next) => {
    const authStore = useAuthStore()
    
    // 需要认证的路由
    if (to.meta.requiresAuth) {
      if (authStore.isLoggedIn) {
        // 检查权限
        if (to.meta.roles && !to.meta.roles.includes(authStore.user.role)) {
          next('/forbidden')
          return
        }
        next()
      } else {
        // 保存目标路由,登录后重定向
        authStore.setRedirectPath(to.fullPath)
        next('/login')
      }
    } else {
      next()
    }
  })
  
  // 登录后重定向
  router.afterEach((to) => {
    const authStore = useAuthStore()
    if (to.path === '/login' && authStore.redirectPath) {
      const redirect = authStore.redirectPath
      authStore.clearRedirectPath()
      router.push(redirect)
    }
  })
}

// main.ts 中使用
import { setupAuthGuards } from './router/guards'
setupAuthGuards(router)

配置路由元信息:

const routes = [
  {
    path: '/public',
    component: PublicPage,
    meta: { requiresAuth: false }
  },
  {
    path: '/dashboard',
    component: Dashboard,
    meta: {
      requiresAuth: true,
      roles: ['user', 'admin'],
      title: 'Dashboard'
    }
  },
  {
    path: '/admin',
    component: AdminPanel,
    meta: {
      requiresAuth: true,
      roles: ['admin'],
      title: 'Admin Panel'
    }
  }
]

🔄 状态保持与恢复

9. 滚动位置管理

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      // 返回前进/后退时保存的位置
      return savedPosition
    } else if (to.hash) {
      // 跳转到锚点
      return { el: to.hash, behavior: 'smooth' }
    } else {
      // 其他情况滚到顶部
      return { top: 0, behavior: 'smooth' }
    }
  }
})

10. 动态修改路由

// 运行时添加新路由
router.addRoute({
  path: '/new-page',
  component: NewPage
})

// 添加到特定父路由
router.addRoute('DashboardParent', {
  path: 'new-tab',
  component: NewTab
})

// 删除路由
const removeRoute = router.addRoute({
  path: '/temp',
  component: TempPage
})
removeRoute()  // 调用返回值以删除

📈 性能优化最佳实践

性能对比表

优化方案首屏加载路由切换包大小推荐度
普通导入
路由懒加载⭐⭐⭐⭐⭐⭐⭐⭐
预加载路由⭐⭐⭐⭐⭐
分组懒加载⭐⭐⭐⭐⭐⭐⭐⭐⭐

关键优化技巧

// 1. 合理分组懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import(/* webpackChunkName: "admin" */ '../layouts/AdminLayout.vue'),
    children: [
      { path: 'home', component: () => import(/* webpackChunkName: "admin" */ '../pages/DashboardHome.vue') },
      { path: 'users', component: () => import(/* webpackChunkName: "admin" */ '../pages/Users.vue') }
    ]
  }
]

// 2. 预加载关键路由
if (isMobile()) {
  // 移动端不预加载,节省带宽
} else {
  // 桌面端预加载用户常访问的页面
  const link = document.createElement('link')
  link.rel = 'prefetch'
  link.href = '/js/page.chunk.js'
  document.head.appendChild(link)
}

// 3. 使用 preload vs prefetch
// preload:立即加载,用于当前页面立即需要的资源
// prefetch:空闲时加载,用于未来可能需要的资源

🐛 常见问题解决

问题 1:路由参数变化但组件不更新

<script setup lang="ts">
import { useRoute } from 'vue-router'
import { watch } from 'vue'

const route = useRoute()
const data = ref(null)

// ❌ 错误:只在组件挂载时执行一次
const loadData = async () => {
  data.value = await fetchUserData(route.params.id)
}

// ✅ 正确:监听路由变化
watch(() => route.params.id, async (newId) => {
  data.value = await fetchUserData(newId)
}, { immediate: true })
</script>

问题 2:导航守卫中的死循环

// ❌ 错误:可能导致无限循环
router.beforeEach((to, from, next) => {
  if (needsRedirect(to)) {
    next('/other-page')  // 这会再次触发 beforeEach
  }
})

// ✅ 正确:检查来源以避免循环
router.beforeEach((to, from, next) => {
  if (needsRedirect(to) && !isAlreadyRedirected(from)) {
    next('/other-page')
  } else {
    next()
  }
})

问题 3:组件 keep-alive 与路由结合

<!-- App.vue -->
<template>
  <keep-alive :include="cachedComponents">
    <router-view />
  </keep-alive>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

// 缓存特定路由的组件
const cachedComponents = computed(() => {
  return ['Dashboard', 'List']  // 组件名称
})
</script>

🎓 最佳实践总结

路由设计原则

  1. 语义化 URL/users/123/page?id=123 更清晰
  2. 一致的命名:保持路由路径的命名规范
  3. 按需加载:大型应用使用路由懒加载
  4. 层级明确:使用嵌套路由表示页面层级关系
  5. 保护敏感路由:使用导航守卫实现权限控制

性能建议

// ✅ DO: 使用路由懒加载
const UserProfile = () => import('../pages/UserProfile.vue')

// ✅ DO: 分组相关路由
component: () => import(/* webpackChunkName: "admin" */ '../layouts/Admin.vue')

// ✅ DO: 利用元信息管理配置
meta: { requiresAuth: true, title: 'Page Title' }

// ❌ DON'T: 导入所有路由组件
import * as Pages from '../pages'

// ❌ DON'T: 在导航守卫中做重操作
// 应该在组件中异步加载数据

📚 扩展阅读


总结

Vue Router 4 提供了强大而灵活的路由系统:

  • 基础路由:简单直观的声明式配置
  • 高级功能:导航守卫、懒加载、嵌套路由
  • 性能优化:通过合理的代码分割提升用户体验
  • 类型安全:完整的 TypeScript 支持

掌握这些概念和技巧,你就能构建高效、可维护的单页应用。