第 126 章:Vue Router 路由管理
学习目标
- 掌握 Vue Router 4 的安装与基本配置
- 理解动态路由、嵌套路由、命名路由
- 学会路由守卫、懒加载、过渡动画
- 能在项目中用 TypeScript 集成路由
一、为什么需要 Vue Router
SPA(单页应用)必须在前端实现路由切换,不能依赖浏览器跳转。Vue Router 是 Vue 官方路由管理器。
二、安装与基本配置
2.1 安装
bash
# npm
npm install vue-router@4
# pnpm
pnpm add vue-router@42.2 创建路由表
typescript
// src/router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'about',
component: () => import('@/views/About.vue')
},
{
// 404
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue')
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;2.3 main.ts 注入
typescript
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');2.4 路由出口
vue
<!-- App.vue -->
<template>
<router-view />
</template>三、路由模式
3.1 HTML5 History
typescript
// 推荐:URL 不含 #,需要服务器配合
createWebHistory();
createWebHistory(import.meta.env.BASE_URL);nginx
# Nginx 配置示例
location / {
try_files $uri $uri/ /index.html;
}3.2 Hash 模式
typescript
// URL 含 #,无需服务器配置
createWebHashHistory();text
https://example.com/#/about3.3 Memory 模式
typescript
// SSR 或测试环境
createMemoryHistory();四、动态路由
4.1 路径参数
typescript
{
path: '/users/:id',
component: () => import('@/views/UserDetail.vue')
}vue
<!-- 跳转 -->
<router-link :to="`/users/${userId}`">用户详情</router-link>
<!-- 组件中获取 -->
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id); // 字符串
</script>4.2 多参数
typescript
// 路由配置
{ path: '/search/:category/:keyword', component: Search }
// 跳转
router.push('/search/books/vue');
// 组件
const { category, keyword } = route.params;4.3 可选参数
typescript
// ?表示可选
{ path: '/posts/:id?', component: PostList }
// 匹配 /posts 和 /posts/1234.4 通配符
typescript
{
// 任意路径
path: '/:pathMatch(.*)*',
component: NotFound
}
// 路由参数
route.params.pathMatch; // 'foo/bar'五、嵌套路由
5.1 配置
typescript
{
path: '/admin',
component: () => import('@/layouts/Admin.vue'),
children: [
{
path: '',
component: () => import('@/views/admin/Dashboard.vue')
},
{
path: 'users',
component: () => import('@/views/admin/Users.vue')
},
{
path: 'settings',
component: () => import('@/views/admin/Settings.vue')
}
]
}5.2 父布局
vue
<!-- Admin.vue -->
<template>
<div class="admin">
<aside>
<router-link to="/admin">仪表盘</router-link>
<router-link to="/admin/users">用户</router-link>
<router-link to="/admin/settings">设置</router-link>
</aside>
<main>
<!-- 子路由出口 -->
<router-view />
</main>
</div>
</template>六、命名路由与编程式导航
6.1 命名路由
typescript
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/User.vue')
}6.2 命名跳转
vue
<router-link :to="{ name: 'user', params: { id: 123 } }">用户</router-link>typescript
router.push({ name: 'user', params: { id: 123 } });
router.push({ path: '/user/123' });6.3 编程式导航
typescript
import { useRouter } from 'vue-router';
const router = useRouter();
// 跳转
router.push('/about');
router.push({ name: 'about' });
// 替换(不留历史)
router.replace('/login');
// 后退/前进
router.back();
router.forward();
router.go(-2); // 后退两步6.4 命名视图
typescript
{
path: '/',
components: {
default: Home,
sidebar: Sidebar,
header: Header
}
}vue
<router-view />
<router-view name="sidebar" />
<router-view name="header" />七、路由传参
7.1 Query 参数
typescript
// 跳转
router.push({ path: '/search', query: { q: 'vue', page: 2 } });
// 读取
const { q, page } = route.query;7.2 Params
typescript
// 跳转
router.push({ name: 'user', params: { id: 123 } });
// 读取
const { id } = route.params;7.3 状态(state)
typescript
router.push({
path: '/result',
state: { from: 'home' }
});
// 读取
console.log(history.state.from);八、路由守卫
8.1 全局前置守卫
typescript
router.beforeEach((to, from) => {
// 必须返回 false / 路由地址 / true
if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'login', query: { redirect: to.fullPath } };
}
return true;
});8.2 全局后置守卫
typescript
router.afterEach((to, from) => {
// 常用于埋点、滚动
document.title = (to.meta.title as string) || 'Default';
window.scrollTo(0, 0);
});8.3 路由独享守卫
typescript
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
if (!isAdmin()) return false;
}
}8.4 组件内守卫
vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
onBeforeRouteUpdate((to, from) => {
// 当前路由变化但组件复用
console.log('路由更新');
});
onBeforeRouteLeave((to, from) => {
// 离开前
if (hasUnsavedChanges) {
return confirm('确定离开?');
}
});
</script>8.5 组合式封装
typescript
// composables/useAuthGuard.ts
export function useAuthGuard() {
const router = useRouter();
function requireAuth() {
if (!isLoggedIn()) {
router.push({ name: 'login', query: { redirect: router.currentRoute.value.fullPath } });
return false;
}
return true;
}
return { requireAuth };
}九、懒加载
9.1 动态导入
typescript
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue')
}9.2 分组打包
typescript
// 同一组的路由打包到一个 chunk
const Admin = () => import(/* webpackChunkName: "admin" */ '@/views/Admin.vue');
const Users = () => import(/* webpackChunkName: "admin" */ '@/views/Users.vue');9.3 Vite 配置
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia']
}
}
}
}
});十、过渡动画
10.1 路由过渡
vue
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>10.2 滚动行为
typescript
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) return savedPosition;
if (to.hash) return { el: to.hash, behavior: 'smooth' };
return { top: 0 };
}
});十一、TypeScript 增强
11.1 路由元信息类型
typescript
// types/router.d.ts
import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean;
title?: string;
roles?: string[];
}
}11.2 路由参数类型
typescript
// typed-router.ts
import 'vue-router';
declare module 'vue-router' {
interface RouteParamValue {}
interface RouteParams {
id?: string;
slug?: string;
}
}11.3 路由对象
typescript
const route = useRoute();
// route.params.id 自动推断为 string | undefined十二、错误处理
12.1 解析错误
typescript
router.onError((error) => {
if (error.message.includes('Failed to fetch')) {
console.log('路由 chunk 加载失败');
}
});12.2 重试机制
typescript
component: () => import('@/views/Dashboard.vue').catch(() => import('@/views/Error.vue'))十三、本章小结
| 概念 | 关键 |
|---|---|
| 路由模式 | web(hash 含 #)/ history(HTML5) |
| 动态路由 | :id 路径参数 |
| 嵌套路由 | children + 二级 router-view |
| 编程式导航 | router.push / replace / go |
| 路由守卫 | beforeEach / beforeEnter / onBeforeRouteLeave |
| 懒加载 | 动态 import, 按需打包 |
| 路由元 | meta 字段,扩展类型 |
动手练习
- 用户路由:实现
/users/:id详情页,展示用户信息 - 嵌套路由:实现后台管理布局,包含侧边栏和子路由
- 路由守卫:实现登录拦截,未登录跳转登录页
- 懒加载:将所有页面改为动态 import
- 滚动行为:实现路由切换到顶部与 hash 锚点
推荐阅读
- 📖 Vue Router 官方文档 — 权威指南
- 📖 Vue Router 4 迁移指南 — 从 v3 升级
- 🌐 Vue Router 最佳实践 — 源码与示例
- 📖 SPA History 模式部署 — 服务器配置
下一章:第 127 章:Pinia 状态管理 →