Vue3 Vue Router 4 路由完全指南

小飞兽 Vue.js 106 次阅读 2026-05-03

Vue3 Vue Router 4 路由完全指南

概述

Vue Router 4 是 Vue3 的官方路由管理器,负责将 URL 映射到组件,实现单页应用(SPA)的页面切换功能。Vue Router 4 全面兼容 Vue3 Composition API,支持嵌套路由、路由守卫、懒加载路由、路由元信息等高级特性,是构建现代 Vue 单页应用的核心基础设施。

基础概念

  • <strong>路由</strong>:URL 到组件的映射规则,如 <code>/home</code> → HomeComponent
  • <strong>路由表</strong>:定义所有路由规则的对象数组
  • <strong>router-view</strong>:占位组件,渲染匹配到的路由组件
  • <strong>router-link</strong>:声明式导航链接,替代 <code>&lt;a&gt;</code> 标签(防止页面刷新)
  • <strong>动态路由</strong>:URL 参数传递,如 <code>/user/:id</code>
  • <strong>嵌套路由</strong>:父路由组件内部的子路由渲染

基础语法

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/user/:id', component: User },  // 动态路由
  { path: '/redirect', redirect: '/home' } // 重定向
]

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

// 路由守卫
router.beforeEach((to, from) => {
  // to: 目标路由信息
  // from: 来源路由信息
  return true // 返回 true 继续导航,或返回 false 取消
})

完整代码示例

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

// 路由懒加载:只有访问时才加载组件,减少首屏加载时间
const Home = () => import('./views/Home.vue')
const About = () => import('./views/About.vue')
const UserProfile = () => import('./views/UserProfile.vue')
const Dashboard = () => import('./views/Dashboard.vue')
const Settings = () => import('./views/Settings.vue')
const NotFound = () => import('./views/NotFound.vue')

// 路由配置数组
const routes = [
  // 首页
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { title: '首页', requiresAuth: false }
  },

  // 嵌套路由:/dashboard 下有多个子路由
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: Dashboard,
    meta: { requiresAuth: true },
    children: [
      {
        path: '',                  // /dashboard 默认显示
        redirect: to => ({ name: 'Overview' })
      },
      {
        path: 'overview',           // /dashboard/overview
        name: 'Overview',
        component: () => import('./views/Overview.vue'),
        meta: { title: '概览' }
      },
      {
        path: 'analytics',          // /dashboard/analytics
        name: 'Analytics',
        component: () => import('./views/Analytics.vue'),
        meta: { title: '数据分析', roles: ['admin'] }  // 角色权限限制
      },
      {
        path: 'reports',            // /dashboard/reports
        name: 'Reports',
        component: () => import('./views/Reports.vue'),
        meta: { title: '报表' }
      }
    ]
  },

  // 用户详情:动态路由参数
  {
    path: '/user/:id',
    name: 'UserProfile',
    component: UserProfile,
    // props: true 将路由 params 转为组件 props(更易测试)
    props: true,
    meta: { title: '用户资料' }
  },

  // 设置页
  {
    path: '/settings',
    name: 'Settings',
    component: Settings,
    meta: { requiresAuth: true }
  },

  // 路由别名
  {
    path: '/about-us',
    alias: '/about',
    component: About
  },

  // 404 放最后,匹配所有未定义路径
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: NotFound,
    meta: { title: '页面不存在' }
  }
]

const router = createRouter({
  // createWebHistory:使用 HTML5 History API(URL 美观)
  // createWebHashHistory:使用 hash 模式(兼容旧浏览器,不需要服务器配置)
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  // 滚动行为:路由切换时页面滚动位置
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition  // 浏览器前进/后退时保持之前滚动位置
    }
    if (to.hash) {
      return { el: to.hash, behavior: 'smooth' }  // 锚点滚动
    }
    return { top: 0 }  // 默认滚动到顶部
  }
})

// ========== 路由守卫 ==========

// 全局前置守卫:所有导航都会触发
router.beforeEach((to, from) => {
  // 设置页面标题
  document.title = to.meta.title ? `${to.meta.title} - 我的应用` : '我的应用'

  // 权限验证示例
  const isAuthenticated = checkAuth()  // 模拟检查登录状态

  if (to.meta.requiresAuth && !isAuthenticated) {
    // 未登录,跳转到登录页(并记录原本想去的页面)
    return { name: 'Login', query: { redirect: to.fullPath } }
  }

  return true  // 允许导航
})

// 全局后置守卫:导航确认后触发(适合日志、滚动控制等)
router.afterEach((to, from) => {
  console.log(`导航完成: ${from.path} -> ${to.path}`)
})

// 路由独享守卫(单路由配置中使用 beforeEnter)
// {
//   path: '/admin',
//   component: Admin,
//   beforeEnter: (to, from) => {
//     return to.meta.isAdmin ? true : { name: 'Home' }
//   }
// }

function checkAuth() {
  return localStorage.getItem('token') !== null
}

export default router

// ========== main.js ==========
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 ==========
<script setup>
import { useRouter, useRoute } from 'vue-router'
import { computed } from 'vue'

const router = useRouter()
const route = useRoute()

// 当前路由信息
const currentPath = computed(() => route.path)
const routeName = computed(() => route.name)

// 判断链接是否激活
function isActive(name) {
  return route.name === name
}
</script>

<template>
  <div id="app">
    <!-- 顶部导航栏 -->
    <nav class="navbar">
      <div class="nav-brand">
        <router-link to="/">🏠 首页</router-link>
      </div>
      <div class="nav-links">
        <router-link to="/" :class="{ active: isActive('Home') }">Home</router-link>
        <router-link to="/dashboard/overview" :class="{ active: isActive('Overview') }">仪表盘</router-link>
        <router-link to="/about" :class="{ active: isActive('About') }">关于</router-link>
        <router-link to="/settings" :class="{ active: isActive('Settings') }">设置</router-link>
        <!-- 动态路由跳转 -->
        <router-link :to="{ name: 'UserProfile', params: { id: 42 } }">
          用户 #42
        </router-link>
      </div>
    </nav>

    <!-- 路由出口:匹配到的组件在这里渲染 -->
    <main class="main-content">
      <!-- 切换时的过渡动画 -->
      <router-view v-slot="{ Component, route }">
        <transition name="fade" mode="out-in">
          <component :is="Component" :key="route.path" />
        </transition>
      </router-view>
    </main>

    <!-- 当前路由信息(调试用) -->
    <footer class="debug-info">
      <code>当前路由: {{ routeName }} | 路径: {{ currentPath }}</code>
    </footer>
  </div>
</template>

<style scoped>
.navbar {
  display: flex;
  align-items: center;
  padding: 1rem;
  background: #2c3e50;
  color: white;
  gap: 1rem;
}
.nav-brand { font-weight: bold; font-size: 1.2rem; }
.nav-links { display: flex; gap: 1rem; }
.navbar a {
  color: #a0c4e8;
  text-decoration: none;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  transition: all 0.2s;
}
.navbar a:hover, .navbar a.active {
  background: rgba(255,255,255,0.1);
  color: white;
}
.main-content { padding: 1rem; min-height: 60vh; }
.debug-info {
  padding: 0.5rem;
  background: #f8f8f8;
  border-top: 1px solid #ddd;
  font-size: 0.8rem;
}

/* 路由切换动画 */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.25s ease;
}
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>

// ========== Dashboard.vue(父路由组件)==========
<script setup>
</script>

<template>
  <div class="dashboard">
    <aside class="sidebar">
      <router-link to="/dashboard/overview">📊 概览</router-link>
      <router-link to="/dashboard/analytics">📈 数据分析</router-link>
      <router-link to="/dashboard/reports">📄 报表</router-link>
    </aside>
    <!-- 子路由出口:嵌套路由的组件在这里渲染 -->
    <section class="dashboard-content">
      <router-view />
    </section>
  </div>
</template>

<style scoped>
.dashboard { display: flex; gap: 1rem; }
.sidebar {
  width: 180px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.sidebar a {
  padding: 0.5rem;
  text-decoration: none;
  color: #2c3e50;
  border-radius: 4px;
}
.sidebar a:hover, .sidebar a.router-link-active {
  background: #42b983;
  color: white;
}
.dashboard-content { flex: 1; padding: 1rem; border: 1px solid #ddd; border-radius: 8px; }
</style>

运行效果说明

顶部导航栏提供全局路由链接,点击"仪表盘"进入嵌套路由页面,左侧侧边栏显示子路由链接,右侧内容区渲染当前子路由组件。通过 URL 参数如 /user/42 访问用户详情,组件通过 props: true 自动接收 id 参数。访问不存在的路径自动跳转到 404 页面。路由切换时内容区有淡入淡出过渡动画。

常见问题

Q1:路由懒加载怎么做?和不懒加载有什么区别?

懒加载:const Home = () => import('./views/Home.vue'),Webpack 会为每个懒加载路由生成独立 JS chunk,访问时才下载,减少首屏 bundle 大小。不懒加载:import Home from './views/Home.vue',所有组件打包到一个 JS 文件,首屏加载慢。生产环境强烈推荐懒加载。

Q2:<code>router.push</code> 和 <code>router.replace</code> 有什么区别?

router.push 会向 history 栈添加新记录,点击浏览器后退可以返回上一页。router.replace 替换当前 history 记录,不产生新记录,适合表单提交后禁止返回重复提交等场景。<router-link> 的默认行为是 push,加 replace 属性变为 replace

Q3:嵌套路由的子路由 path 要不要加 <code>/</code> 前缀?

子路由 path 不要/,如 path: 'overview' 相对父路由拼接成 /dashboard/overview。加了 / 则变成绝对路径,独立于父路由。如果父路由有默认子路由(path: ''),访问父路由 /dashboard 时会自动显示默认子路由内容。

延伸阅读

  • <a href="#">Vue3 组合式 API 完全指南</a> —— 在 Vue Router 页面组件中使用组合式 API
  • <a href="#">Vue3 Pinia 状态管理完全指南</a> —— 路由状态与 Pinia store 的结合使用
  • <a href="#">Vue3 进阶项目结构设计</a> —— 大型项目中路由表的组织、路由拆分与按需加载优化