路由与权限控制
Vue Router 4 是 Vue 官方路由库,与 Vue 3 配套使用。见 Vue 基础。
Vue Router 是 Vue.js 官方的路由管理器,与 Vue 3 深度集成。合理运用动态路由、导航守卫、元信息、懒加载等特性,可以构建具有权限控制、优雅过渡、良好性能的单页应用。
一、动态路由
动态路由允许将路径中的一部分作为参数传递给组件,实现同一组件对应不同内容。
1. 路由参数的匹配模式
| 模式 | 定义方式 | 匹配示例 | route.params(组合式 API) |
|---|---|---|---|
| 必选参数 | :id | /user/123 | { id: '123' } |
| 可选参数 | :id? | /user 或 /user/456 | {} 或 { id: '456' } |
| 通配(catch-all) | /:pathMatch(.*)* | /files/a/b/c | { pathMatch: ['a','b','c'] }(数组,需 props 转换时可用字符串) |
| 自定义正则 | :id(\\d+) | /user/123 | { id: '123' }(非数字不匹配) |
Vue Router 4 已移除 Vue Router 3 的
*、+重复参数语法,通配须使用带自定义正则的参数名(见 官方迁移说明)。
示例路由配置:
const routes = [
{ path: "/user/:id", component: User },
{ path: "/user/:id?", component: UserOptional },
{ path: "/files/:pathMatch(.*)*", name: "files", component: Files },
{ path: "/post/:id(\\d+)", component: Post },
];
2. 路由参数的响应式监听
在组件内部,需要监听路由参数变化以重新获取数据。组合式 API 提供两种方式:
- 使用
watch监听$route:
import { useRoute } from "vue-router";
import { watch } from "vue";
const route = useRoute();
watch(
() => route.params.id,
(newId, oldId) => {
// 当 id 变化时重新获取数据
fetchData(newId);
},
);
- 使用导航守卫
onBeforeRouteUpdate(推荐,避免重复获取数据):
import { onBeforeRouteUpdate } from "vue-router";
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
await fetchData(to.params.id);
}
});
二、路由导航守卫体系
Vue Router 4 提供了三种类型的守卫,用于在跳转前后执行逻辑,如权限验证、页面标题修改、数据预加载等。
1. 全局守卫
beforeEach:在导航触发时调用,适合进行全局权限检查。beforeResolve:在所有组件内守卫和异步路由组件被解析之后、导航被确认之前调用。afterEach:导航成功完成后调用,不可改变导航,适合打点统计、页面标题更新。
router.beforeEach((to, from) => {
// 返回 false 取消导航
// 返回 { name: 'Login' } 重定向到登录页
if (!isAuthenticated && to.meta.requiresAuth) {
return { name: "Login" };
}
return true;
});
2. 路由独享守卫
在路由配置中直接定义 beforeEnter,仅对该路由生效。
const routes = [
{
path: "/admin",
component: Admin,
beforeEnter: (to, from) => {
if (!isAdmin) return { name: "Home" };
return true;
},
},
];
3. 组件内守卫
使用组合式 API 中的 onBeforeRouteLeave 和 onBeforeRouteUpdate。
onBeforeRouteLeave:离开当前组件时触发(如阻止未保存的编辑)。onBeforeRouteUpdate:同一组件因路由参数变化而复用时触发。
import { onBeforeRouteLeave } from "vue-router";
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges) {
return confirm("您有未保存的更改,确定要离开吗?");
}
return true;
});
4. Vue Router 4 中 next 的演进
在 Vue Router 3 中,守卫必须调用 next() 来决定导航行为,这容易导致错误(如多次调用)。Vue Router 4 废弃了 next 参数,推荐直接返回:
- 返回
true(或undefined)→ 继续导航。 - 返回
false→ 取消导航。 - 返回一个路由地址对象(如
{ name: 'Login' })→ 重定向。 - 抛出错误 → 导航被取消。
// Vue Router 3 风格(不推荐)
router.beforeEach((to, from, next) => {
if (!isAuth) next("/login");
else next();
});
// Vue Router 4 推荐风格
router.beforeEach((to, from) => {
if (!isAuth) return "/login";
return true;
});
这种写法更简洁、不易出错,且天然支持异步(返回 Promise)。
三、路由元信息(meta)
在路由配置中添加 meta 字段,用于附加任意数据,如权限标识、布局组件、页面缓存等。
const routes = [
{
path: "/admin",
component: Admin,
meta: {
requiresAuth: true,
roles: ["admin", "super_admin"],
layout: "AdminLayout",
keepAlive: true,
},
},
];
基于 meta 的权限控制
在全局 beforeEach 中读取 to.meta,判断用户权限。
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated) {
return "/login";
}
if (to.meta.roles && !to.meta.roles.includes(userRole)) {
return "/forbidden";
}
return true;
});
布局切换
在根组件中根据路由 meta.layout 动态渲染不同的布局组件。
<template>
<component :is="$route.meta.layout || 'DefaultLayout'">
<RouterView />
</component>
</template>
页面缓存策略
结合 <KeepAlive>,通过 meta.keepAlive 控制是否缓存组件。
<template>
<RouterView v-slot="{ Component }">
<KeepAlive :include="cachedViews">
<component :is="Component" />
</KeepAlive>
</RouterView>
</template>
<script setup>
import { computed } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const cachedViews = computed(() => (route.meta.keepAlive ? [route.name] : []));
</script>
四、路由懒加载的深度实践
1. 动态导入与代码分割原理
使用 () => import('./Foo.vue') 定义路由组件,Webpack / Vite 会自动将每个动态导入的组件打包成独立的 chunk,仅在访问该路由时加载。
const routes = [{ path: "/about", component: () => import("./views/About.vue") }];
2. 配合 Webpack Magic Comments 控制 chunk 命名
在 import() 中添加注释,可以指定 chunk 名称,便于调试和缓存管理。
const UserProfile = () => import(/* webpackChunkName: "user-profile" */ "./views/UserProfile.vue");
const AdminPanel = () => import(/* webpackChunkName: "admin-panel" */ "./views/AdminPanel.vue");
Vite 则使用 rollupOptions.output.chunkFileNames 或注释 /* vitePreload */ 等方式控制。
3. 懒加载的潜在问题与改善
- 加载闪烁:可配合
Suspense或路由守卫中的加载进度条(如 NProgress)。 - 预加载:使用
webpackPrefetch: true在空闲时预加载;jsconst Home = () => import(/* webpackPrefetch: true */ "./Home.vue");
五、路由过渡动画
通过 <RouterView> 配合 <Transition> 可以为路由切换添加平滑动画。注意设置 key 以确保不同路由之间的过渡效果。
<template>
<RouterView v-slot="{ Component, route }">
<Transition name="fade" mode="out-in">
<component :is="Component" :key="route.path" />
</Transition>
</RouterView>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
可以基于路由的 meta 定义不同的过渡效果(如左滑、右滑)。
六、路由滚动行为控制
使用 scrollBehavior 方法控制导航后页面的滚动位置。
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, left: 0, behavior: "smooth" };
},
});
可以结合 meta 实现特定路由保持滚动位置。
七、路由历史记录模式
| 模式 | 创建方式 | URL 示例 | 适用场景 |
|---|---|---|---|
| HTML5 History | createWebHistory() | https://example.com/user/123 | 需要干净的 URL,服务端需配置 fallback(所有路由回退到 index.html) |
| Hash | createWebHashHistory() | https://example.com/#/user/123 | 纯静态站点(无服务端支持)、兼容旧浏览器,无需服务端配置 |
注意:使用 createWebHistory 时,线上服务器必须进行配置(如 Nginx 将所有未匹配路径指向 index.html),否则刷新页面会出现 404。
八、完整权限控制示例(组合式 API)
结合 createRouter、pinia 存储用户权限和登录状态。
// router/index.js
import { createRouter, createWebHistory } from "vue-router";
import { useUserStore } from "@/stores/user";
const routes = [
{ path: "/login", component: () => import("@/views/Login.vue") },
{
path: "/dashboard",
component: () => import("@/views/Dashboard.vue"),
meta: { requiresAuth: true, roles: ["user", "admin"] },
},
{
path: "/admin",
component: () => import("@/views/Admin.vue"),
meta: { requiresAuth: true, roles: ["admin"] },
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach(async (to, from) => {
const userStore = useUserStore();
// 检查是否需要登录
if (to.meta.requiresAuth && !userStore.isLoggedIn) {
return { name: "login", query: { redirect: to.fullPath } };
}
// 角色校验
if (to.meta.roles && !to.meta.roles.includes(userStore.role)) {
return { name: "forbidden" };
}
return true;
});
export default router;
总结
| 主题 | 核心要点 |
|---|---|
| 动态路由 | :id、:id?、:pathMatch(.*)*;useRoute + watch 或 onBeforeRouteUpdate 监听参数变化 |
| 导航守卫 | 全局(beforeEach、afterEach)、路由独享(beforeEnter)、组件内(onBeforeRouteLeave/Update);Vue Router 4 推荐直接返回布尔值/路由地址,弃用 next |
| 路由元信息 | 在 meta 中附加权限、布局、缓存等数据,在守卫中读取并控制 |
| 懒加载 | () => import() 自动代码分割,配合 Magic Comments 命名 chunk |
| 过渡动画 | <RouterView v-slot> + <Transition> 实现路由级转场动画 |
| 滚动行为 | scrollBehavior 控制导航后的滚动位置,支持 savedPosition 和 hash |
| 历史模式 | createWebHistory(需服务端支持)、createWebHashHistory(纯前端) |
掌握以上知识,可以构建功能完善、性能优良且安全可靠的前端路由系统。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| Vue Router 文档 | 中文 |
| Vue Router:动态路由 | 参数与通配 |
| Vue Router:导航守卫 | 守卫 |
| Vue Router 4 迁移 | 从 v3 迁移 |
相关文章
资源优化
图片懒加载、按需引入与 Web Vitals 监控。见 HTML 图片、CSS 字体优化。
状态管理:Pinia 与 Vuex
Pinia 是 Vue 官方推荐的状态管理库(Vue 3 新项目默认选型)。Vuex 4 仍可与 Vue 3 配合,用于维护旧项目。见 Vue 基础。
Vue 生态关键库深度集成
VueUse、Vite 与 Nuxt 是 Vue 3 生态中常用的效率与工程化方案。概览见 Vue 基础。
依赖收集与派发更新机制
说明 Vue 3 中 track / trigger 与 targetMap 的工作方式。前置:v2-v3 差异。
测试策略
Vue 应用常用 Vitest + @vue/test-utils 做单元/组件测试,Playwright 或 Cypress 做 E2E。见 Vue 基础。
组合式 API 与逻辑复用设计模式
组合式 API 与 Composables 是 Vue 3 推荐的逻辑组织方式。概览见 Vue 基础。