FORMA

路由与权限控制

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 的 *+ 重复参数语法,通配须使用带自定义正则的参数名(见 官方迁移说明)。

示例路由配置:

js
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
js
import { useRoute } from "vue-router";
import { watch } from "vue";
const route = useRoute();
watch(
  () => route.params.id,
  (newId, oldId) => {
    // 当 id 变化时重新获取数据
    fetchData(newId);
  },
);
  • 使用导航守卫 onBeforeRouteUpdate(推荐,避免重复获取数据):
js
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:导航成功完成后调用,不可改变导航,适合打点统计、页面标题更新。
js
router.beforeEach((to, from) => {
  // 返回 false 取消导航
  // 返回 { name: 'Login' } 重定向到登录页
  if (!isAuthenticated && to.meta.requiresAuth) {
    return { name: "Login" };
  }
  return true;
});

2. 路由独享守卫

在路由配置中直接定义 beforeEnter,仅对该路由生效。

js
const routes = [
  {
    path: "/admin",
    component: Admin,
    beforeEnter: (to, from) => {
      if (!isAdmin) return { name: "Home" };
      return true;
    },
  },
];

3. 组件内守卫

使用组合式 API 中的 onBeforeRouteLeaveonBeforeRouteUpdate

  • onBeforeRouteLeave:离开当前组件时触发(如阻止未保存的编辑)。
  • onBeforeRouteUpdate:同一组件因路由参数变化而复用时触发。
js
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' })→ 重定向。
  • 抛出错误 → 导航被取消。
js
// 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 字段,用于附加任意数据,如权限标识、布局组件、页面缓存等。

js
const routes = [
  {
    path: "/admin",
    component: Admin,
    meta: {
      requiresAuth: true,
      roles: ["admin", "super_admin"],
      layout: "AdminLayout",
      keepAlive: true,
    },
  },
];

基于 meta 的权限控制

在全局 beforeEach 中读取 to.meta,判断用户权限。

js
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 动态渲染不同的布局组件。

vue
<template>
  <component :is="$route.meta.layout || 'DefaultLayout'">
    <RouterView />
  </component>
</template>

页面缓存策略

结合 <KeepAlive>,通过 meta.keepAlive 控制是否缓存组件。

vue
<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,仅在访问该路由时加载。

js
const routes = [{ path: "/about", component: () => import("./views/About.vue") }];

2. 配合 Webpack Magic Comments 控制 chunk 命名

import() 中添加注释,可以指定 chunk 名称,便于调试和缓存管理。

js
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 在空闲时预加载;
    js
    const Home = () => import(/* webpackPrefetch: true */ "./Home.vue");
    

五、路由过渡动画

通过 <RouterView> 配合 <Transition> 可以为路由切换添加平滑动画。注意设置 key 以确保不同路由之间的过渡效果。

vue
<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 方法控制导航后页面的滚动位置。

js
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 HistorycreateWebHistory()https://example.com/user/123需要干净的 URL,服务端需配置 fallback(所有路由回退到 index.html)
HashcreateWebHashHistory()https://example.com/#/user/123纯静态站点(无服务端支持)、兼容旧浏览器,无需服务端配置

注意:使用 createWebHistory 时,线上服务器必须进行配置(如 Nginx 将所有未匹配路径指向 index.html),否则刷新页面会出现 404。

八、完整权限控制示例(组合式 API)

结合 createRouterpinia 存储用户权限和登录状态。

js
// 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 + watchonBeforeRouteUpdate 监听参数变化
导航守卫全局(beforeEachafterEach)、路由独享(beforeEnter)、组件内(onBeforeRouteLeave/Update);Vue Router 4 推荐直接返回布尔值/路由地址,弃用 next
路由元信息meta 中附加权限、布局、缓存等数据,在守卫中读取并控制
懒加载() => import() 自动代码分割,配合 Magic Comments 命名 chunk
过渡动画<RouterView v-slot> + <Transition> 实现路由级转场动画
滚动行为scrollBehavior 控制导航后的滚动位置,支持 savedPositionhash
历史模式createWebHistory(需服务端支持)、createWebHashHistory(纯前端)

掌握以上知识,可以构建功能完善、性能优良且安全可靠的前端路由系统。

参考文献

以下链接在编写时均可正常访问: