Vue.js: Vue Router 路由
最后更新:2026-08-26
Vue Router 是 Vue 官方的路由管理器——让你的 SPA(单页应用)有多个"页面",每个 URL 对应一个组件。Vue Router 4 是 Vue 3 配套版本,完全支持 Composition API。
SPA 路由的核心是URL ↔ 组件的映射。掌握 Vue Router 4 是开发任何非 trivial Vue 应用的必备技能。
1. 你将学到
- createRouter + createWebHistory 初始化
- 5 种路由:静态、动态、嵌套、命名、通配
- 编程式导航(router.push / replace / go)
- 路由懒加载(动态 import)
- 路由参数(params / query)
- 路由守卫(beforeEach / beforeEnter)
- 5 大实战场景
2. 一个 SPA 的"404 噩梦"
(1) 痛点:5 个页面,5 份重复代码
Alice 的后台有 5 个页面(Home/Products/Orders/Users/Settings):
VUE
<!-- ❌ 翻车版:用 v-if 切换 5 个组件 -->
<template>
<div>
<button @click="currentPage = 'home'">Home</button>
<button @click="currentPage = 'products'">Products</button>
<button @click="currentPage = 'orders'">Orders</button>
<div v-if="currentPage === 'home'"><HomePage /></div>
<div v-else-if="currentPage === 'products'"><ProductsPage /></div>
<div v-else-if="currentPage === 'orders'"><OrdersPage /></div>
<div v-else>Page not found</div>
</div>
</template>
4 个问题:
- URL 不变:刷新页面丢失当前页面
- 无法分享链接:不能复制 URL 给同事
- 无浏览器前进/后退:用户体验差
- SEO 差:搜索引擎看不懂
(2) Vue Router 4 解法:5 行路由 + 5 个组件
JS
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/products', component: () => import('@/views/ProductsPage.vue') },
{ path: '/orders', component: () => import('@/views/OrdersPage.vue') },
{ path: '/users', component: () => import('@/views/UsersPage.vue') },
{ path: '/settings', component: () => import('@/views/SettingsPage.vue') }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
VUE
<!-- App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/products">Products</router-link>
<router-link to="/orders">Orders</router-link>
</nav>
<!-- ✅ 关键:router-view 渲染当前路由的组件 -->
<router-view />
</template>
5 行路由 + 1 个 router-view = 完整 SPA。URL 自动同步,浏览器前进/后退工作。
(3) 收益
使用 Vue Router 后:
- URL 同步:自动同步,刷新保留
- 可分享:任何页面都有 URL
- 浏览器历史:前进/后退正常工作
- SEO 友好:SSR 支持(Nuxt 3)
- 代码量:5 个 v-if → 5 行 routes 数组(-80%)
3. 路由初始化
(1) 完整初始化代码
JS
// src/router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
// 1. 定义路由(懒加载)
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/products', component: () => import('@/views/ProductsPage.vue') }
]
// 2. 创建 router 实例
const router = createRouter({
// history 模式:URL 无 #(推荐)
history: createWebHistory(),
// hash 模式:URL 带 #(兼容老浏览器)
// history: createWebHashHistory(),
routes,
// 滚动行为:路由切换时滚到顶部
scrollBehavior(to, from, savedPosition) {
return savedPosition || { top: 0 }
}
})
export default router
(2) main.js 注册
JS
// src/main.js
const { createApp } = Vue
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router) // 注册路由
app.mount('#app')
(3) 2 大 history 模式对比
| 维度 | createWebHistory | createWebHashHistory |
|---|---|---|
| URL 形式 | /products |
/#/products |
| SEO | ✅ 友好 | ❌ 不友好 |
| 服务器配置 | 需要(try_files) | 不需要 |
| 浏览器兼容 | IE10+ | IE8+ |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
4. 5 种路由配置
(1) 静态路由
JS
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/about', component: () => import('@/views/AboutPage.vue') },
{ path: '/contact', component: () => import('@/views/ContactPage.vue') }
]
(2) 动态路由(参数)
JS
const routes = [
// /products/123
{
path: '/products/:id',
component: () => import('@/views/ProductDetailPage.vue')
},
// /users/456/posts/789
{
path: '/users/:userId/posts/:postId',
component: () => import('@/views/PostPage.vue')
},
// 可选参数
{
path: '/news/:category?',
component: () => import('@/views/NewsPage.vue')
}
]
VUE
<!-- ProductDetailPage.vue 接收参数 -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // '123'
</script>
(3) 嵌套路由
JS
const routes = [
{
path: '/users',
component: () => import('@/views/UsersPage.vue'),
children: [
{
path: '', // 默认子路由
component: () => import('@/views/UsersListPage.vue')
},
{
path: ':id', // /users/123
component: () => import('@/views/UserDetailPage.vue')
}
]
}
]
VUE
<!-- UsersPage.vue -->
<template>
<div>
<h1>Users</h1>
<router-view /> <!-- 子路由出口 -->
</div>
</template>
(4) 命名路由
JS
const routes = [
{
path: '/products/:id',
name: 'product-detail', // 命名路由
component: () => import('@/views/ProductDetailPage.vue')
}
]
VUE
<!-- 通过 name 跳转,避免硬编码 URL -->
<router-link :to="{ name: 'product-detail', params: { id: 123 } }">
Product 123
</router-link>
(5) 通配符 404
JS
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
// ... 其他路由
{
path: '/:pathMatch(.*)*', // 匹配所有未定义的路由
component: () => import('@/views/NotFoundPage.vue')
}
]
5. 5 种导航方式
(1) 声明式导航(router-link)
VUE
<template>
<!-- 字符串路径 -->
<router-link to="/products">Products</router-link>
<!-- 命名路由(推荐) -->
<router-link :to="{ name: 'products' }">Products</router-link>
<!-- 动态参数 -->
<router-link :to="{ name: 'product-detail', params: { id: 123 } }">
Product 123
</router-link>
<!-- Query 参数 -->
<router-link :to="{ path: '/search', query: { q: 'vue' } }">
Search
</router-link>
</template>
(2) 编程式导航(router.push)
JS
import { useRouter } from 'vue-router'
const router = useRouter()
function goToProduct(id) {
router.push(`/products/${id}`) // 字符串
// 或
router.push({ name: 'product-detail', params: { id } }) // 对象
}
function goBack() {
router.back() // 后退
}
function goForward() {
router.forward() // 前进
}
function replaceCurrent() {
router.replace('/login') // 替换(不留历史)
}
(3) 5 种 push vs replace 区别
| 方式 | 行为 | 历史记录 |
|---|---|---|
router.push() |
跳转到新页面 | 留下记录(可后退) |
router.replace() |
替换当前页面 | 不留记录(不可后退) |
router.go(n) |
前进/后退 n 步 | 走历史 |
router.back() |
后退一步 | 走历史 |
router.forward() |
前进一步 | 走历史 |
(4) 编程式 vs 声明式对比
| 场景 | 推荐 |
|---|---|
| 用户点击链接 | router-link(声明式) |
| JS 触发跳转 | router.push(编程式) |
| 表单提交后跳转 | router.push |
| 登录后跳转 | router.replace |
| 404 自动回退 | router.back |
6. 路由守卫(导航守卫)
(1) 3 种守卫类型
JS
// 1. 全局前置守卫(最常用)
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
next('/login') // 跳登录
} else {
next() // 放行
}
})
// 2. 路由独享守卫
const routes = [
{
path: '/admin',
component: () => import('@/views/AdminPage.vue'),
beforeEnter: (to, from, next) => {
if (isAdmin()) next()
else next('/403')
}
}
]
// 3. 组件内守卫
// 组件内:beforeRouteEnter / beforeRouteUpdate / beforeRouteLeave
(2) 完整登录守卫示例
JS
// router/index.js
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const isLoggedIn = !!token
// 需要登录的页面
if (to.meta.requiresAuth && !isLoggedIn) {
return next({
name: 'login',
query: { redirect: to.fullPath } // 登录后跳回
})
}
// 已登录访问登录页
if (to.name === 'login' && isLoggedIn) {
return next({ name: 'home' })
}
next()
})
JS
// 路由 meta 配置
const routes = [
{
path: '/dashboard',
component: () => import('@/views/DashboardPage.vue'),
meta: { requiresAuth: true } // 需要登录
}
]
7. 完整示例:电商后台路由系统
▶ 示例:手写 Hash 路由器(CDN 模拟 SPA 路由)
HTML
📖 仅展示
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<style>
a { padding: 6px 12px; margin: 2px; text-decoration: none; color: #333; }
a.active { background: #42b883; color: white; border-radius: 4px; }
.page { padding: 1rem; border: 1px solid #ddd; margin-top: 0.5rem; min-height: 80px; }
</style>
<div id="app">
<nav>
<a href="#/" :class="{ active: route === '/' }">Home</a>
<a href="#/about" :class="{ active: route === '/about' }">About</a>
<a href="#/user/123" :class="{ active: route === '/user/123' }">User 123</a>
<a href="#/products" :class="{ active: route === '/products' }">Products</a>
</nav>
<div class="page">
<component :is="currentPage"></component>
</div>
<p style="color: #999; font-size: 0.85rem;">
当前路由: {{ route }} | 历史栈: {{ history.length }} 步
</p>
<button @click="goBack" :disabled="history.length <= 1">← 后退</button>
<button @click="goForward" :disabled="!canForward">前进 →</button>
</div>
<script>
const { createApp, ref, computed, onMounted, onUnmounted } = Vue
// 5 个页面组件
const Home = { template: '<h3>🏠 Home</h3><p>这是首页</p>' }
const About = { template: '<h3>ℹ️ About</h3><p>关于我们</p>' }
const User = {
props: ['id'],
template: '<h3>👤 User {{ id }}</h3><p>用户资料页</p>'
}
const Products = {
template: '<h3>📦 Products</h3><p>商品列表页(动态参数示例:<code>useRoute().params.id</code>)</p>'
}
const NotFound = { template: '<h3>❌ 404 Not Found</h3>' }
// 路由表
const routes = {
'/': Home,
'/about': About,
'/user/123': User,
'/products': Products
}
const App = {
components: { Home, About, User, Products, NotFound },
setup() {
const route = ref(window.location.hash.slice(1) || '/')
const history = ref([route.value])
const forwardStack = ref([])
function updateRoute() {
const newRoute = window.location.hash.slice(1) || '/'
if (newRoute !== route.value) {
history.value.push(newRoute)
forwardStack.value = []
route.value = newRoute
}
}
onMounted(() => window.addEventListener('hashchange', updateRoute))
onUnmounted(() => window.removeEventListener('hashchange', updateRoute))
function goBack() {
if (history.value.length > 1) {
const last = history.value.pop()
forwardStack.value.push(last)
const prev = history.value[history.value.length - 1]
window.location.hash = prev
}
}
function goForward() {
if (forwardStack.value.length > 0) {
const next = forwardStack.value.pop()
history.value.push(next)
window.location.hash = next
}
}
// 解析动态参数
const currentPage = computed(() => {
if (routes[route.value]) {
// 动态路由:/user/123 匹配 User 组件
if (route.value.startsWith('/user/')) {
return { ...User, props: { id: route.value.split('/')[2] } }
}
return routes[route.value]
}
return NotFound
})
const canForward = computed(() => forwardStack.value.length > 0)
return { route, history, currentPage, goBack, goForward, canForward }
}
}
createApp(App).mount('#app')
</script>
⚠️ 此为教学演示 Hash 路由原理。生产项目用
vue-router 官方库(必须 Vite)。
▶ 示例:5 种导航方式速查
| 方式 | 代码 | 适用 |
|---|---|---|
| 声明式链接 | <router-link to="/products"> |
用户点击链接 |
| 字符串路径 | router.push('/products') |
JS 跳转 |
| 命名路由 | router.push({ name: 'products' }) |
推荐(重命名不破坏) |
| 动态参数 | router.push({ name: 'user', params: { id: 123 } }) |
详情页 |
| Query 参数 | router.push({ path: '/search', query: { q: 'vue' } }) |
搜索/过滤 |
▶ 示例:完整登录守卫(⚠️ 需 Vite 项目)
JS
// router/index.js
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const isLoggedIn = !!token
if (to.meta.requiresAuth && !isLoggedIn) {
return next({ name: 'login', query: { redirect: to.fullPath } })
}
if (to.name === 'login' && isLoggedIn) {
return next({ name: 'home' })
}
next()
})
// 路由 meta 配置
const routes = [
{
path: '/dashboard',
component: () => import('@/views/DashboardPage.vue'),
meta: { requiresAuth: true } // 需要登录
}
]
▶ 示例:5 大常见错误速查
| 错误 | 现象 | 解决 |
|---|---|---|
| router-view 缺失 | 页面不显示 | 添加 <router-view /> |
| 路由没懒加载 | 首屏慢 | 用 () => import() 动态导入 |
| 守卫死循环 | 跳不出 | 避免 next() 又触发 beforeEach |
| 嵌套路由无 outlet | 子页面不显示 | 父组件加 <router-view /> |
| 404 路由缺失 | 报错 | 加 path: '/:pathMatch(.*)*' |
▶ 示例:5 大性能对比
| 模式 | 首屏 | 切换 | 适用 |
|---|---|---|---|
| 静态 import | 慢 | 快 | 小项目 |
| 动态 import | ⭐⭐⭐⭐⭐ | 首次慢 | 推荐 |
| 路由分组懒加载 | ⭐⭐⭐⭐⭐ | 首次慢 | 大项目 |
| prefetch | ⭐⭐⭐⭐ | 快 | 用户体验优先 |
| SSR | ⭐⭐⭐⭐⭐ | 快 | Nuxt 3 |
❓ 常见问题
Q createWebHistory 和 createWebHashHistory 选哪个?
A createWebHistory(URL 无 #,SEO 友好)需要服务器配置;createWebHashHistory(URL 带 #)兼容老浏览器。生产环境推荐 createWebHistory。
Q 动态路由怎么接收参数?
A 用
useRoute() 钩子:const route = useRoute(); route.params.id。Q router.push 和 router.replace 区别?
A push 留历史记录(可后退),replace 替换当前(不留历史)。登录后用 replace(不能后退回登录页)。
Q 路由守卫能异步吗?
A 能。
beforeEach(async (to, from, next) => { await checkAuth(); next() })。但要小心避免死循环。Q 嵌套路由怎么实现?
A 父路由用
children: [] 配置子路由,父组件模板中加 <router-view /> 作为子路由出口。Q Vue Router 4 和 Vue Router 3 区别?
A Vue Router 4 支持 Composition API(useRouter/useRoute),TypeScript 推断更强,导航守卫参数变了(to/from/next)。Vue 3 项目必须用 Vue Router 4。
Q 路由懒加载能提升多少性能?
A 首屏可减少 50-80% 加载时间(不加载未访问的页面)。大项目(10+ 页面)效果显著。
Q 路由守卫 meta 字段怎么用?
A 路由配置加
meta: { requiresAuth: true },守卫中用 to.meta.requiresAuth 访问。用于权限、标题、面包屑等。📖 小节
- Vue Router 4 是 Vue 3 官方路由管理器
- 5 种路由:静态、动态、嵌套、命名、通配符
- 5 种导航:router-link、router.push、replace、back、forward
- 3 种守卫:全局(beforeEach)、路由独享(beforeEnter)、组件内
- 路由懒加载(动态 import)减少首屏
- createWebHistory 推荐,createWebHashHistory 兼容老浏览器
- 嵌套路由用 children +
<router-view />
📝 作业
-
基础题(难度⭐) 实现一个简单的多页 SPA:
- 3 个页面:Home / About / Contact
- 顶部导航 + 底部 router-view
- 用
vue-router配置路由
-
进阶题(难度⭐⭐) 实现电商商品路由:
/products列表页/products/:id详情页(动态参数)/products/:id/edit编辑页- 404 处理
-
挑战题(难度⭐⭐⭐) 实现完整的电商后台路由系统:
- 10+ 页面(home/products/orders/users/settings/login/404 等)
- 嵌套路由(/admin/dashboard, /admin/products 等)
- 路由懒加载(首屏快)
- 登录守卫(未登录跳 /login,登录后跳回)
- 5 种导航方式全覆盖
- 编程式导航(点击按钮跳转)
- TypeScript 强类型 routes 配置