Vue3 Vue Router 4 路由完全指南
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><a></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> —— 大型项目中路由表的组织、路由拆分与按需加载优化