Vue Router 路由系统
2026/3/20大约 8 分钟
Vue Router 完全指南:构建单页应用的路由系统
Vue Router 是 Vue.js 的官方路由管理器,它与 Vue.js 核心深度集成,使构建单页面应用变得轻而易举。本文将全面介绍 Vue Router 的使用,涵盖 Vue2 的 Vue Router 3.x 和 Vue3 的 Vue Router 4.x。
基础概念
什么是前端路由?
传统网站是多页应用(MPA),每次跳转都会请求新的 HTML 页面。而单页应用(SPA)通过前端路由,在不刷新页面的情况下切换视图。
传统网站(MPA):
用户点击 → 请求服务器 → 返回新页面 → 浏览器渲染
单页应用(SPA):
用户点击 → 前端路由拦截 → JavaScript 更新视图 → 无需请求
路由模式
// Hash 模式:使用 URL hash(#)
// http://example.com/#/user/123
// 兼容性好,不需要服务器配置
// History 模式:使用 HTML5 History API
// http://example.com/user/123
// URL 更美观,需要服务器配置支持
Vue Router 安装与配置
Vue2 + Vue Router 3.x
npm install vue-router@3
// router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "@/views/Home.vue";
import About from "@/views/About.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: About,
},
];
const router = new VueRouter({
mode: "history", // 或 'hash'
base: process.env.BASE_URL,
routes,
});
export default router;
// main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
Vue3 + Vue Router 4.x
npm install vue-router@4
// router/index.js
import {
createRouter,
createWebHistory,
createWebHashHistory,
} from "vue-router";
import Home from "@/views/Home.vue";
import About from "@/views/About.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: About,
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
// 或 history: createWebHashHistory(),
routes,
});
export default router;
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
createApp(App).use(router).mount("#app");
路由配置
基础路由
const routes = [
// 基本路由
{ path: "/", component: Home },
// 命名路由
{ path: "/about", name: "about", component: About },
// 路由别名
{ path: "/user", component: User, alias: "/u" },
// 重定向
{ path: "/home", redirect: "/" },
{ path: "/home", redirect: { name: "about" } },
{
path: "/home",
redirect: (to) => {
return { path: "/search", query: { q: to.params.searchText } };
},
},
// 404 处理
// Vue Router 3.x
{ path: "*", component: NotFound },
// Vue Router 4.x
{ path: "/:pathMatch(.*)*", name: "NotFound", component: NotFound },
];
动态路由
const routes = [
// 动态参数
{ path: "/user/:id", component: User },
// 多个参数
{ path: "/user/:userId/post/:postId", component: Post },
// 可选参数(Vue Router 4.x)
{ path: "/user/:id?", component: User },
// 正则匹配(Vue Router 4.x)
{ path: "/user/:id(\\d+)", component: User }, // 只匹配数字
{ path: "/article/:slug([a-z]+)", component: Article },
// 可重复参数
{ path: "/files/:path+", component: Files }, // 至少一个
{ path: "/files/:path*", component: Files }, // 零个或多个
];
// 组件中获取参数
// Vue2 Options API
export default {
computed: {
userId() {
return this.$route.params.id;
},
},
};
// Vue3 Composition API
import { useRoute } from "vue-router";
const route = useRoute();
const userId = route.params.id;
嵌套路由
const routes = [
{
path: "/user/:id",
component: User,
children: [
// 默认子路由(空路径)
{ path: "", component: UserHome },
// /user/:id/profile
{ path: "profile", component: UserProfile },
// /user/:id/posts
{ path: "posts", component: UserPosts },
],
},
];
<!-- User.vue -->
<template>
<div class="user">
<h2>用户 {{ $route.params.id }}</h2>
<!-- 子路由出口 -->
<router-view />
</div>
</template>
命名视图
const routes = [
{
path: "/",
components: {
default: Main,
sidebar: Sidebar,
header: Header,
},
},
];
<template>
<div>
<router-view name="header" />
<router-view name="sidebar" />
<router-view />
<!-- default -->
</div>
</template>
路由导航
声明式导航
<template>
<!-- 基础链接 -->
<router-link to="/about">关于</router-link>
<!-- 命名路由 -->
<router-link :to="{ name: 'user', params: { id: 123 } }"> 用户 </router-link>
<!-- 带查询参数 -->
<router-link :to="{ path: '/search', query: { q: 'vue' } }">
搜索
</router-link>
<!-- 替换当前历史记录 -->
<router-link to="/about" replace>关于</router-link>
<!-- 自定义激活类名 -->
<router-link
to="/about"
active-class="active"
exact-active-class="exact-active"
>
关于
</router-link>
<!-- 自定义标签 -->
<router-link to="/about" custom v-slot="{ navigate, href, isActive }">
<a :href="href" @click="navigate" :class="{ active: isActive }"> 关于 </a>
</router-link>
</template>
编程式导航
// Vue2 Options API
export default {
methods: {
goToUser() {
// 字符串路径
this.$router.push("/user/123");
// 对象形式
this.$router.push({ path: "/user/123" });
// 命名路由
this.$router.push({ name: "user", params: { id: 123 } });
// 带查询参数
this.$router.push({ path: "/search", query: { q: "vue" } });
// 替换当前记录
this.$router.replace("/about");
// 前进/后退
this.$router.go(1); // 前进一步
this.$router.go(-1); // 后退一步
this.$router.back(); // 后退
this.$router.forward(); // 前进
},
},
};
// Vue3 Composition API
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
function goToUser() {
router.push({ name: "user", params: { id: 123 } });
}
// 获取当前路由信息
console.log(route.params.id);
console.log(route.query.q);
console.log(route.path);
console.log(route.fullPath);
导航守卫
全局守卫
// 全局前置守卫
router.beforeEach((to, from, next) => {
// to: 即将进入的路由
// from: 当前离开的路由
// next: 必须调用来 resolve 这个钩子
const isAuthenticated = store.getters.isAuthenticated;
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: "login", query: { redirect: to.fullPath } });
} else {
next();
}
});
// Vue Router 4.x 新语法(返回值)
router.beforeEach((to, from) => {
const isAuthenticated = store.getters.isAuthenticated;
if (to.meta.requiresAuth && !isAuthenticated) {
return { name: "login", query: { redirect: to.fullPath } };
}
// 返回 true 或不返回值表示通过
});
// 全局解析守卫(在组件内守卫和异步路由组件解析之后)
router.beforeResolve(async (to) => {
if (to.meta.requiresData) {
try {
await fetchData(to.params.id);
} catch (error) {
return { name: "error" };
}
}
});
// 全局后置钩子
router.afterEach((to, from, failure) => {
// 不接受 next,不会改变导航
// 适合做分析、更改页面标题等
document.title = to.meta.title || "默认标题";
// Vue Router 4.x 可以检测导航失败
if (failure) {
console.error("Navigation failed:", failure);
}
});
路由独享守卫
const routes = [
{
path: "/admin",
component: Admin,
beforeEnter: (to, from, next) => {
// 只在进入此路由时触发
if (!isAdmin()) {
next({ name: "forbidden" });
} else {
next();
}
},
},
// Vue Router 4.x 支持数组
{
path: "/admin",
component: Admin,
beforeEnter: [checkAuth, checkAdmin],
},
];
组件内守卫
// Vue2 Options API
export default {
beforeRouteEnter(to, from, next) {
// 在渲染组件之前调用
// 不能访问 this(组件实例还没创建)
next((vm) => {
// 通过 vm 访问组件实例
vm.fetchData();
});
},
beforeRouteUpdate(to, from, next) {
// 路由变化但组件被复用时调用
// 例如 /user/1 → /user/2
// 可以访问 this
this.user = null;
this.fetchUser(to.params.id);
next();
},
beforeRouteLeave(to, from, next) {
// 离开组件对应路由时调用
// 可以访问 this
if (this.hasUnsavedChanges) {
const confirmed = window.confirm("有未保存的更改,确定离开?");
if (confirmed) {
next();
} else {
next(false);
}
} else {
next();
}
},
};
// Vue3 Composition API
import { onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";
// 注意:没有 onBeforeRouteEnter,因为 setup 时组件已创建
onBeforeRouteUpdate((to, from) => {
// 路由更新时
});
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const confirmed = window.confirm("有未保存的更改,确定离开?");
return confirmed;
}
});
导航解析流程
1. 导航被触发
2. 在失活的组件里调用 beforeRouteLeave
3. 调用全局 beforeEach
4. 在重用的组件里调用 beforeRouteUpdate
5. 在路由配置里调用 beforeEnter
6. 解析异步路由组件
7. 在被激活的组件里调用 beforeRouteEnter
8. 调用全局 beforeResolve
9. 导航被确认
10. 调用全局 afterEach
11. 触发 DOM 更新
12. 调用 beforeRouteEnter 中传给 next 的回调函数
路由元信息
const routes = [
{
path: "/admin",
component: Admin,
meta: {
requiresAuth: true,
roles: ["admin"],
title: "管理后台",
breadcrumb: [
{ name: "首页", path: "/" },
{ name: "管理后台", path: "/admin" },
],
},
},
];
// 在守卫中使用
router.beforeEach((to, from) => {
// 检查是否需要认证
if (to.meta.requiresAuth) {
// ...
}
// 检查角色
if (to.meta.roles && !hasRole(to.meta.roles)) {
return { name: "forbidden" };
}
});
// 在组件中使用
// Vue3
const route = useRoute();
console.log(route.meta.title);
路由懒加载
const routes = [
// 基础懒加载
{
path: "/about",
component: () => import("@/views/About.vue"),
},
// 带魔法注释(Webpack)
{
path: "/admin",
component: () =>
import(
/* webpackChunkName: "admin" */
"@/views/Admin.vue"
),
},
// 分组懒加载
{
path: "/admin/users",
component: () =>
import(/* webpackChunkName: "admin" */ "@/views/AdminUsers.vue"),
},
{
path: "/admin/settings",
component: () =>
import(/* webpackChunkName: "admin" */ "@/views/AdminSettings.vue"),
},
];
滚动行为
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 }
}
})
// 延迟滚动
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ top: 0, behavior: 'smooth' })
}, 500)
})
}
动态路由
// 添加路由
router.addRoute({
path: "/new-route",
component: NewComponent,
});
// 添加嵌套路由
router.addRoute("parentName", {
path: "child",
component: ChildComponent,
});
// 删除路由
const removeRoute = router.addRoute(routeRecord);
removeRoute(); // 调用返回的函数即可删除
// 或通过名称删除
router.removeRoute("routeName");
// 检查路由是否存在
router.hasRoute("routeName");
// 获取所有路由
router.getRoutes();
动态路由实战:权限路由
// 基础路由(无需权限)
const constantRoutes = [
{ path: "/login", component: Login },
{ path: "/404", component: NotFound },
];
// 需要权限的路由
const asyncRoutes = [
{
path: "/admin",
component: Admin,
meta: { roles: ["admin"] },
children: [
{ path: "users", component: AdminUsers, meta: { roles: ["admin"] } },
],
},
{
path: "/dashboard",
component: Dashboard,
meta: { roles: ["admin", "editor"] },
},
];
// 根据用户角色过滤路由
function filterAsyncRoutes(routes, roles) {
return routes.filter((route) => {
if (route.meta?.roles) {
const hasRole = route.meta.roles.some((role) => roles.includes(role));
if (!hasRole) return false;
}
if (route.children) {
route.children = filterAsyncRoutes(route.children, roles);
}
return true;
});
}
// 登录后动态添加路由
async function initRoutes() {
const userRoles = await fetchUserRoles();
const accessRoutes = filterAsyncRoutes(asyncRoutes, userRoles);
accessRoutes.forEach((route) => {
router.addRoute(route);
});
// 添加 404 兜底路由(必须最后添加)
router.addRoute({ path: "/:pathMatch(.*)*", redirect: "/404" });
}
路由过渡动画
<template>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
</template>
<style>
/* 淡入淡出 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 滑动效果 */
.slide-enter-active,
.slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter-from {
transform: translateX(100%);
}
.slide-leave-to {
transform: translateX(-100%);
}
</style>
常见场景处理
登录重定向
router.beforeEach((to, from) => {
const isAuthenticated = store.getters.isAuthenticated;
if (to.meta.requiresAuth && !isAuthenticated) {
// 保存目标路由,登录后跳转
return {
name: "login",
query: { redirect: to.fullPath },
};
}
});
// 登录成功后
async function handleLogin() {
await login(form);
const redirect = route.query.redirect || "/";
router.push(redirect);
}
页面标题
router.afterEach((to) => {
document.title = to.meta.title || "默认标题";
});
页面加载进度条
import NProgress from "nprogress";
import "nprogress/nprogress.css";
router.beforeEach(() => {
NProgress.start();
});
router.afterEach(() => {
NProgress.done();
});
路由缓存
<template>
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>
</template>
<script setup>
import { computed } from "vue";
import { useAppStore } from "@/stores/app";
const appStore = useAppStore();
const cachedViews = computed(() => appStore.cachedViews);
</script>
总结
Vue Router 是构建 Vue 单页应用的核心工具:
- 路由配置:支持动态路由、嵌套路由、命名视图
- 导航方式:声明式(router-link)和编程式(router.push)
- 导航守卫:全局、路由、组件级别的守卫
- 路由元信息:附加自定义数据
- 懒加载:按需加载提升性能
- 动态路由:运行时添加/删除路由
掌握这些知识,你就能构建出功能完善的单页应用路由系统。下一篇文章将介绍 Vue 的状态管理方案:Vuex 和 Pinia。