路由导航守卫
全局守卫(作用于所有路由,并支持注册多个)
router.beforeEach
- 全局前置守卫可以使用 router.beforeEach 注册,跳转前触发,常用于登录验证等需要跳转前告知的情况;
- 当导航触发时,全局前置守卫按照创建顺序调用;守卫是异步解析执行,导航会等待所有守卫解析完成后才会继续;
- 案例:
// 登录验证守卫 router.beforeEach(async (to: RouteLocationNormalized) => { const authStore = useAuthStore(); const isAuthenticated = authStore.isAuthenticated; // 需要认证但未登录 if (to.meta.requiresAuth && !isAuthenticated) { return { path: "/login", query: { redirect: to.fullPath }, }; } // 已登录但访问登录/注册页 if ((to.path === "/login" || to.path === "/register") && isAuthenticated) { return "/"; } }); // 权限控制守卫 router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { const authStore = useAuthStore(); const requiredRoles = to.meta.roles as UserRole[]; if (requiredRoles) { const hasPermission = requiredRoles.some((role) => authStore.userRoles.includes(role) ); if (!hasPermission) { next({ name: "forbidden" }); return; } } next(); } );
router.beforeResolve
-
全局解析守卫在 beforeEach 之后,在导航被确认之前调用,在所有组件内守卫和异步路由组件解析完成后执行,是进行最后数据获取或验证的理想位置;
-
案例:
TypeScriptTypeScript// 1. 页面访问权限最终检查 router.beforeResolve((to, from) => { const authStore = useAuthStore(); // 在最终确认前再次检查权限 if (to.meta.requiresSpecialPermission && !authStore.checkSpecialPermission()) { return { name: "unauthorized" }; } });
// 2. 预加载关键资源 router.beforeResolve(async (to) => { // 预加载下一页可能需要的资源 if (to.name === "product-detail") { await preloadProductImages(to.params.id as string); } if (to.name === "dashboard") { await preloadDashboardCharts(); } });
router.afterEach
-
afterEach 在导航完成后调用,适合执行不需要影响导航的后续操作,如分析跟踪、页面滚动等;
-
案例:
TypeScriptTypeScriptTypeScript// 1. 页面访问统计 router.afterEach((to, from, failure) => { if (!failure) { // 发送页面浏览统计 trackPageView(to.fullPath); // 或使用分析工具 if (window.gtag) { gtag("event", "page_view", { page_path: to.fullPath, page_title: to.meta.title || "Untitled" }); } } }); function trackPageView(path: string) { // 实现你的统计逻辑 console.log("Page viewed:", path); }
// 2. 页面标题管理 router.afterEach((to) => { // 设置页面标题 const appName = "My Awesome App"; document.title = to.meta.title ? `${to.meta.title} | ${appName}` : appName; // 更新meta描述 updateMetaDescription(to.meta.description); }); function updateMetaDescription(description?: string) { const metaDesc = document.querySelector('meta[name="description"]'); if (metaDesc) { metaDesc.setAttribute("content", description || "默认描述"); } }
// 3. 滚动行为控制 router.afterEach((to) => { // 滚动到顶部(如果不需要特殊滚动行为) if (to.meta.scrollToTop !== false) { window.scrollTo(0, 0); } // 或者更精细的控制 if (to.hash) { const el = document.querySelector(to.hash); if (el) { el.scrollIntoView({ behavior: "smooth" }); } } });
路由独享守卫(只作用于特定路由)
beforeEnter
-
在路由配置上直接定义 beforeEnter 守卫,和全局的 beforeEnter 完全相同,如果都设置则在 beforeEnter 之后紧随执行;
-
案例:
TypeScriptTypeScript// 1. 管理员路由守卫 const routes: RouteRecordRaw[] = [ { path: "/admin", name: "admin", component: () => import("@/views/AdminView.vue"), meta: { requiresAuth: true }, beforeEnter: (to, from) => { const authStore = useAuthStore(); if (authStore.userRole !== "admin") { return { name: "forbidden" }; } }, }, ];
// 2. 动态参数验证 const routes: RouteRecordRaw[] = [ { path: "/user/:id(\\d+)", // 只匹配数字ID name: "user", component: () => import("@/views/UserView.vue"), beforeEnter: (to) => { if (!/^\d+$/.test(to.params.id as string)) { return { name: "not-found" }; } }, }, ];
组件内守卫(在路由组件内定义)
beforeRouteEnter
-
在渲染该组件的对应路由被验证前调用,此时组件还未创建;
-
案例:
// 数据预加载 onBeforeRouteEnter(async (to, from, next) => { try { await fetchUser(to.params.id as string); next(); } catch (error) { next({ name: "error" }); } });
beforeRouteUpdate
-
在当前路由改变,但该组件被复用时调用 (例如对于带有动态参数的路径 /foo/:id,当从 /foo/1 跳转到 /foo/2 时会被调用);
-
案例:
onBeforeRouteUpdate(async (to, from) => { try { await fetchUser(to.params.id as string); } catch (error) { // 处理错误 } });
beforeRouteLeave
-
在导航离开前调用;
-
案例:
const formData = ref(/* ... */); const isDirty = ref(false); onBeforeRouteLeave((to, from) => { if (isDirty.value) { const confirmLeave = window.confirm("您有未保存的更改,确定要离开吗?"); if (!confirmLeave) return false; } });
return、next() 控制路由行为
next()(传统方式)
-
这是 Vue Router 3 和 4 都支持的标准方式,必须显式调用 next(),否则导航会被阻塞;
-
常见用法:
router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isLoggedIn()) { next('/login'); // 重定向到登录页 } else { next(); // 放行导航 } });
-
next() 的参数:
参数 行为 next() 正常放行,进入下一个守卫或目标路由 next(false) 中断当前导航,返回 from 路由 next(‘/path’) 或 next({ path: ‘/login’ }) 重定向到新路由 next(error) 导航终止,并触发 router.onError 回调 -
特点:
- ✅ 兼容性好 (Vue Router 3 & 4 都支持);
- ❌ 必须显式调用 next(),否则导航会卡住;
return(Vue Router 4+ 支持,推荐)
-
Vue Router 4 开始支持直接返回一个值来控制导航行为,更符合现代 JavaScript 的编码风格 (类似 async/await);
-
常见用法:
router.beforeEach((to, from) => { if (to.meta.requiresAuth && !isLoggedIn()) { return '/login'; // 重定向到登录页 }; // 不返回任何值(或 return undefined)相当于 next() });
-
返回值的类型:
返回值 行为 undefined 或 true 放行导航 (相当于 next()) false 中断导航 (相当于 next(false)) ‘/path’ 或 { path: ‘/login’ } 重定向 (相当于 next(‘/login’)) Error 实例 导航终止,并触发 router.onError (相当于 next(error)) -
特点
- ✅ 更简洁,避免忘记调用 next() 的问题;
- ✅ 支持 async/await,适合异步操作 (如 API 校验);
- ❌ 仅 Vue Router 4+ 支持 (Vue 3 默认使用 Vue Router 4);
完整的导航解析流程
-
导航被触发
-
调用失活组件的 beforeRouteLeave
-
调用全局的 beforeEach
-
在重用的组件里调用 beforeRouteUpdate
-
调用路由配置里的 beforeEnter
-
解析异步路由组件
-
在被激活的组件里调用 beforeRouteEnter
-
调用全局的 beforeResolve
-
导航被确认
-
调用全局的 afterEach
-
触发 DOM 更新
-
调用 beforeRouteEnter 中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
路由元信息
-
路由元信息是 Vue Router 提供的一个非常有用的功能,它允许在路由配置中添加自定义数据,这些数据可以在导航守卫、组件内访问,用于实现权限控制、页面标题设置、布局选择等功能;
-
基本用法:
- 在路由配置中使用 meta 字段定义元信息:
const routes = [ { path: '/dashboard', component: Dashboard, meta: { requiresAuth: true, // 需要登录 title: '控制面板' // 页面标题 } }, { path: '/public', component: PublicPage, meta: { guestOnly: true // 仅游客可访问 } } ];
- 在导航守卫中访问
router.beforeEach((to, from, next) => { // 检查是否需要认证 if (to.matched.some(record => record.meta.requiresAuth)) { if (!isAuthenticated()) { return '/login'; } } })
- 在组件内访问
const route = useRoute(); // 获取当前路由的元信息 console.log(route.meta); // 获取匹配的所有路由记录的元信息 console.log(route.matched.map(record => record.meta))
路由懒加载
-
路由懒加载的核心思想是:只有当访问某个路由时,才加载对应的组件代码,而不是在应用初始化时就加载所有路由组件 (首屏路由可以不使用懒加载);
-
使用动态 import:
// 下面代码,指定了相同的 webpackChunkName,会合并打包成一个 js 文件,把组件按组分块 { path: '/about', component: () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about') }, { path: '/index', component: () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index') }, { path: '/home', component: () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home') }
路由的几种模式
hash 模式
-
原理:
- 这种模式是前端路由,使用 URL 的 hash 部分来模拟不同的路径;
- 这种模式下的 URL 类似:http://example.com/#/path
- 因为 hash 部分不会被发送到服务器,所以服务端不需要特别处理;
-
实现:
- Vue-Router 通过监听 window.onhashchange 事件来监测 URL 的变化;
- 当 hash 值变化时,Vue-Router 会解析 hash 部分并更新视图;
-
优点:
- 简单易用,不需要服务器配置;
- 浏览器支持良好;
-
缺点:
- URL 不美观,带有 # 符号;
- 对 SEO 不友好,因为 hash 不会被搜索引擎索引;
history 模式
-
原理:
- 利用 HTML5 的 History API *(pushState、replaceState、go、back、forward)*来管理历史记录;
- 这种模式下的 URL 类似:http://example.com/path,没有 #;
- 这种模式需要服务器支持,否则刷新会 404,因为浏览器在请求 URL 时会直接向服务器发送请求;
-
实现:
- Vue-Router 通过监听 window.onpopstate 事件来监测 URL 的变化;
- 使用 router.push 或 router.replace 方法会调用 history.pushState 或 history.replaceState 方法改变 URL;
-
优点:
- URL 美观,解构清晰;
- 更加符合现代单页应用的路由需求;
-
缺点:
- 需要服务器配置,确保所有路径都指向同一个 HTML 文件,以便客户端路由处理;
-
服务器配置示例 (例如 Nginx):
location / { try_files $uri $uri/ /index.html; }
-
案例:
- 服务端代码
const path = require('path') // 导入处理 history 模式的模块 const history = require('connect-history-api-fallback') // 导入 express const express = require('express') const app = express() // 注册处理 history 模式的中间件(后端支持,不使用前端刷新浏览器会显示 404) app.use(history()) // 处理静态资源的中间件(静态资源服务器),网站根目录 ../web(前端打包后的代码放在此处) app.use(express.static(path.join(__dirname, '../web'))) // 开启服务器,端口是 3000 app.listen(3000, () => { console.log('服务器开启,端口:3000') })
- 代码附件下载
- 服务端代码
abstract 模式
-
原理:
- 这种模式主要用于非浏览器环境,比如 Node.js 服务器端渲染时;
- 不依赖于浏览器的 History API 或 hash 变化;
-
实现:
- Vue-Router 使用内存中存储的路由状态来模拟路由行为;
- 没有实际的 URL 变化,完全在代码中管理路由状态;
-
优点:
- 适用于没有浏览器环境的场景,比如服务器端渲染或自动化测试;
-
缺点:
- 只能用于特定场景,不适合普通的前端开发;
vue3🛫 vue3 的变化
上一篇