切换导航
简历
工具
演示
查询
导航菜单
服务端
30
前端
114
数据库
82
进阶开发
8
桌面程序
2
QA
15
软件使用
36
规范协议
5
操作系统
11
AI
0
复用资源
6
硬件
0
标签
HTML
正则表达式
Json
包管理工具
vite
CSS
XPath
JavaScript
Jquery
less
ES6
Vue
React
vue 3
Layui
editor.md
sea.js
Zepto.js
mysql.js
mssql.js
NodeJs
Express
前端template
YAML
杂项
外部链接
.NET Core SDK和运行时安装包
淘宝 NPM 镜像
查询网
Tables Generator线上表格转换工具
引用cdn在线脚本和样式
首页
前端
Vue Router 导航守卫
[TOC] ## 导航守卫时间线 + **横向时间线** ```bash 触发路由跳转 ↓ beforeEach 【全局前置】→ 路由独享beforeEnter → 加载懒加载组件 → 组件内beforeRouteEnter ↓ beforeResolve 【全局解析守卫:组件代码已加载解析,准备挂载】 ↓ afterEach 【全局后置:DOM已渲染,图片/静态资源不一定加载完】 ↓ Vue 执行挂载 → 组件 onMounted(DOM渲染完成)【组件生命周期 → created → beforeMount → mounted】 ↓ window.onload 【浏览器全局:页面所有图片/字体/资源全部加载完毕】 ``` + **横向箭头** beforeEach → beforeEnter → 加载组件 → beforeRouteEnter → beforeResolve → afterEach → onMounted (DOM 挂载) → window.onload + **完整时序(按浏览器真实执行顺序)** - beforeEach(组件还没加载) - 路由独享 beforeEnter - 加载懒加载组件、执行组件内 beforeRouteEnter - beforeResolve(组件代码解析完毕,准备挂载) - 触发 afterEach - Vue 开始执行挂载:创建 DOM、更新页面【组件生命周期 → created → beforeMount → mounted】 - 之后才会加载图片、样式、字体等静态资源 → 最后才是 window.onload + **官方给的完整的导航解析流程** - 导航被触发。 - 在失活的组件里调用 beforeRouteLeave 守卫。 - 调用全局的 beforeEach 守卫。 - 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。 - 在路由配置里调用 beforeEnter。 - 解析异步路由组件。 - 在被激活的组件里调用 beforeRouteEnter。 - 调用全局的 beforeResolve 守卫(2.5+)。 - 导航被确认。 - 调用全局的 afterEach 钩子。 - 触发 DOM 更新。 - 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。 > **Tips:关键节点大白话标注** + beforeEach:刚要走,组件还没下载 + beforeResolve:组件代码下完解析好,就差渲染 + afterEach:路由跳转完成,DOM 已渲染(图片可能还在加载) + onMounted:页面 DOM 已经出来了 + window.onload:整页所有资源加载完毕 [========] ## 导航守卫过程中组件实例状态 以路由跳转 → 组件加载 → 挂载 → 实例可用完整链路,从上到下执行顺序: ```bash 触发路由跳转 ↓ 1️⃣router.beforeEach 【全局前置守卫】 └─ 组件代码未下载、未加载、无实例 ↓ 2️⃣路由独享 beforeEnter └─ 组件开始下载懒加载资源 ↓ 3️⃣组件内 beforeRouteEnter └─ 组件JS加载完成,DOM未创建,无实例 ↓ 4️⃣router.beforeResolve 【全局解析守卫】 └─ ✅ 组件代码全部解析完成 └─ ❌ DOM未渲染、未挂载、**依然拿不到组件实例** ↓ 5️⃣router.afterEach 【全局后置守卫】 └─ ❌ DOM未渲染、未挂载、**依然拿不到组件实例** ↓ 【导航确认】开始渲染DOM ↓ 6️⃣组件挂载 mounted / onMounted 【组件生命周期 → created → beforeMount → mounted】 └─ ✅ DOM挂载完成,**组件实例正式出现,可获取 this / instance** ↓ 7️⃣window.onload └─ 页面所有图片/样式/资源全部加载完成 ``` > Tips:核心节点一句话总结【1️⃣beforeEach:还没下组件、2️⃣beforeResolve:组件代码下载解析完,但没挂载、无实例、3️⃣mounted / onMounted:DOM 挂载,实例才真正存在、4️⃣window.onload:页面全部资源加载完成】 [========] ## 全局前置守卫 beforeEach beforeEach 是 Vue Router 提供的全局前置路由守卫,在路由跳转之前触发,是路由权限控制、跳转拦截、页面状态统一处理的核心 API。 **它的核心作用:**拦截路由跳转 → 执行自定义逻辑 → 决定是否放行 / 重定向 / 取消跳转。 > Tips:一开始就触发,组件还没加载 **基础语法:** ```js // 全局前置守卫 router.beforeEach((to, from, next) => { // 你的逻辑代码 }) ``` ### beforeEach 参数 #### to:即将要进入的目标路由对象 + **类型:**Route(路由对象) + **作用:**获取要跳转到哪里 + **常用属性:** ```js to.path // 目标路由路径,如 /login to.name // 目标路由名称,如 Login to.params // 路由动态参数,如 /user/:id 中的 id to.query // 路由查询参数,如 /list?page=1 中的 page to.meta // 路由元信息(权限、标题、是否需要登录等配置) ``` #### from:当前正要离开的路由对象 + **类型:**Route(路由对象) + **作用:**获取从哪里来 + 属性和 to 完全一致,代表当前所在页面的路由信息。 #### next:路由跳转控制函数(必须调用!) + **作用:**决定路由跳转的最终结果,不调用 next(),路由会卡死,页面无法跳转。 |写法|作用| |--|--| |next()|放行,直接跳转到 to 目标路由| |next(false)|中断当前路由跳转,停留在当前页面| |next('/path') / next({ path: '/login' })|重定向到指定路由| |next(error)|传入 Error 对象,跳转失败并触发 onError 回调| ### beforeEach 返回值 beforeEach 导航守卫返回值有三种类型:布尔值 / 字符串 / 路由对象 > Tips:Vue Router 3.1.0+ 支持直接返回值替代 next(),写法更简洁: + **支持的返回值类型** |类型|解释| |--|--| |布尔值(true / false)|return true:等价于 next(),放行路由<br/>return false:等价于 next(false),拦截路由,取消跳转 |字符串 / 路由对象|return '/login':等价于 next('/login'),重定向到登录页<br/>return { path: '/login', query: { redirect: to.fullPath } }:重定向并携带参数| > **为什么支持两种返回值** >> 简化代码:无需手动调用 next(),直接返回结果即可 语义化:true/false 清晰表示「放行 / 拦截」,字符串 / 对象表示「重定向」 兼容:两种写法(返回值 + next)都支持,适配不同开发习惯 ### beforeEach 阶段可以做的事 #### 路由权限控制 判断用户是否登录,未登录则拦截并跳转到登录页: ```js router.beforeEach((to) => { const isLogin = localStorage.getItem('token') // 需要登录的页面 + 未登录 → 重定向到登录页 if (to.meta.requireAuth && !isLogin) { return '/login' } // 放行 return true }) ``` #### 动态修改页面标题 通过路由元信息统一设置浏览器标签标题: ```js router.beforeEach((to) => { document.title = to.meta.title || '默认标题' return true }) ``` #### 路由跳转拦截 禁止未授权用户访问特定页面、禁止重复跳转当前页面: ```js router.beforeEach((to, from) => { // 禁止重复跳转到当前页面 if (to.path === from.path) return false // 禁止未管理员访问后台页面 if (to.path === '/admin' && !userInfo.isAdmin) return false }) ``` #### 统一处理路由参数 格式化 / 校验路由参数、动态拼接参数: ```js router.beforeEach((to) => { // 无页码时默认跳转到第一页 if (to.path === '/list' && !to.query.page) { return { path: '/list', query: { page: 1 } } } }) ``` ### 加载状态控制 跳转前显示全局加载动画,跳转完成后关闭: ```js router.beforeEach(() => { store.commit('SHOW_LOADING') // 开启loading return true }) // 配合后置守卫关闭loading router.afterEach(() => { store.commit('HIDE_LOADING') }) ``` ### 日志 / 埋点统计 记录用户页面跳转行为,用于数据分析: ```js router.beforeEach((to, from) => { console.log(`从 ${from.path} 跳转到 ${to.path}`) // 上报埋点 return true }) ``` [========] ## 全局解析守卫 beforeResolve **beforeResolve 是:**所有异步路由组件、异步路由依赖资源 都加载完成后,导航正式确认之前 执行的最后一个守卫。 > Tips:所有组件 / 资源都加载好了,就差最后一步渲染,一句话总结:beforeResolve = 所有异步资源加载完毕、跳转最终确认前的最后拦截点。 **基础语法** ```js // router/index.js router.beforeResolve((to, from, next) => { // 逻辑代码 }) ``` beforeResolve 的参数和返回和 beforeEach 基本完全一样,参考 beforeEach 即可 ### beforeResolve 阶段可以做的事 此时所有异步路由组件、组件内守卫、路由懒加载资源都已加载完成,但页面还没渲染、导航还没最终确认。 适合做:最终校验、数据预加载、资源确认、兜底拦截。 #### 最终权限校验(二次校验) beforeEach 做初步登录判断,beforeResolve 做最终权限校验(比如用户权限在跳转过程中被修改): ```js router.beforeResolve(async (to) => { // 从接口获取最新用户权限(确保权限实时有效) const userPermission = await getLatestPermission() // 最终校验:无权限则拦截 if (to.meta.permission && !userPermission.includes(to.meta.permission)) { return '/403' } }) ``` #### 异步路由组件加载后的兜底处理 懒加载组件加载完成后,做最终校验: ```js router.beforeResolve((to) => { // 异步组件已加载完成,判断组件是否存在/有效 if (!to.matched.length) return '/404' }) ``` #### 页面数据预加载(最终版) 在页面渲染前,确保所有依赖资源就绪后,请求页面核心数据: ```js router.beforeResolve(async (to) => { if (to.meta.needData) { // 等待页面数据加载完成再渲染 await store.dispatch('loadPageData', to.params.id) } }) ``` #### 全局跳转统计(精准埋点) 比 beforeEach 更精准,只有真正成功跳转的路由才会触发: ```js router.beforeResolve((to) => { // 上报:用户真正进入了该页面(排除拦截/取消的情况) reportPageView(to.path) }) ``` #### 动态修改路由最终配置 在跳转确认前,最后修改路由参数 / 元信息: ```js router.beforeResolve((to) => { if (to.path === '/goods') { // 最终追加参数 return { ...to, query: { ...to.query, source: 'resolve' } } } }) ``` #### 全局错误兜底拦截 拦截所有异常路由,做统一错误处理: ```js router.beforeResolve((to) => { try { // 校验路由合法性 validateRoute(to) } catch (e) { return '/error' } }) ``` [========] ## 全局后置钩子 afterEach afterEach 全局后置钩子,不会接受 next 函数也不会改变导航本身 **router.afterEach 执行时:**路由已经切换、DOM 已经更新渲染完成,但不等待图片、字体等外部资源加载 想做页面可见后的埋点、统计、进度条结束、状态重置、滚动处理 → 用 afterEach + **基本语法:** ```js router.afterEach((to, from) => { sendToAnalytics(to.fullPath) }) ``` + **完整语法:**携带failure参数,下面将通过一个小章节说明 failure 这个导航失败对象参数 ```js router.afterEach((to, from, failure) => { if (!failure) sendToAnalytics(to.fullPath) }) ``` ### failure 导航失败对象 **作用就是:**告诉你这次路由跳转 —— 到底是【成功了】还是【被中途取消 / 失败了】。 > Tips:导航成功:failure = undefined(没有失败)、导航失败 / 被取消:failure = 一个错误对象(包含失败原因) ```js /** * 导航失败类型枚举 * 0: cancelled 被取消(next(false) / 重复跳转当前路由) * 1: aborted 被中止(守卫内部重定向 next('/xxx')) * 2: notfound 目标路由不存在(404) * 3: duplicate 重复导航(和当前路由完全一样,不会触发跳转) */ ``` 成功:做埋点、滚动、统计 失败:做日志、提示、取消加载动画 **为什么有failure这个参数:**afterEach 不代表 “跳转成功”,只代表 “导航流程结束” **failure 包含什么信息**:它是一个 NavigationFailure 对象,包含: + type:失败类型(0-取消 / 1-重定向 / 2-报错) + from / to:从哪跳哪 + message:原因 **完整示例:**带失败类型判断 + 日志 + 埋点 + 进度条 ```js import { createRouter, createWebHistory, NavigationFailureType } from 'vue-router' import NProgress from 'nprogress' // 自行npm install nprogress import 'nprogress/nprogress.css' NProgress.configure({ showSpinner: false }) const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [], scrollBehavior() { return { top: 0, left: 0 } } }) // 前置守卫:开启进度条 router.beforeEach((to, from, next) => { NProgress.start() next() }) // 后置守卫:进度条结束 + 失败判断 + 埋点 + 日志 router.afterEach((to, from, failure) => { NProgress.done() // 导航成功,执行埋点、滚动 if (!failure) { // 埋点上报 try { window._trackPageView?.({ path: to.fullPath, title: to.meta.title || document.title }) } catch (e) { console.error('埋点异常', e) } return } // 导航失败,精准区分原因 switch (failure.type) { case NavigationFailureType.cancelled: console.warn(`路由取消:${from.fullPath} → ${to.fullPath},next(false)`) break case NavigationFailureType.aborted: console.warn(`路由重定向中止:${from.fullPath} → ${to.fullPath}`) break case NavigationFailureType.notFound: console.warn(`路由不存在:${to.fullPath} 404`) break case NavigationFailureType.duplicate: // 重复跳转当前页面,静默不打印警告(最常见) // console.log(`重复导航:${to.fullPath}`) break default: console.error('未知导航失败', failure) } }) export default router ``` ## 在守卫内的全局注入 ### vue 自带的依赖注入 从 Vue 3.3 开始,使用vue原生的provide、inject 依赖注入,你可以在导航守卫内使用 inject() 方法。这在注入像 pinia stores 这样的全局属性时很有用。在 app.provide() 中提供的所有内容都可以在 router.beforeEach()、router.beforeResolve()、router.afterEach() 内获取到: ```js const app = createApp(App) app.provide('global', 'hello injections') // router.ts or main.ts router.beforeEach((to, from) => { const global = inject('global') // 'hello injections' // a pinia store const userStore = useAuthStore() // ... }) ``` ### 自定义参数注入 除了使用vue自带的provide、inject 依赖注入,我们也可以自己实现一个参数注入 ```js // router.js export function createRouter({ authStore, i18n }) { const router = createRouter({ ... }) router.beforeEach((to, from) => { if (!authStore.isLoggedIn && to.meta.requiresAuth) { console.log(i18n.t('auth.required')) return { name: 'login' } } }) return router } // main.js const authStore = useAuthStore() const router = createRouter({ authStore, i18n }) ``` ## 路由独享的守卫 路由独享守卫(beforeEnter):只有当你即将进入该路由时才会执行。如果跳转到了其他路由,它不会被触发。 ```js const routes = [ { path: '/users/:id', component: UserDetails, beforeEnter: (to, from) => { // reject the navigation return false }, }, ] ``` beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。 你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用: ```js function removeQueryParams(to) { if (Object.keys(to.query).length) return { path: to.path, query: {}, hash: to.hash } } function removeHash(to) { if (to.hash) return { path: to.path, query: to.query, hash: '' } } const routes = [ { path: '/users/:id', component: UserDetails, beforeEnter: [removeQueryParams, removeHash], }, { path: '/about', component: UserDetails, beforeEnter: [removeQueryParams], }, ] ``` ### 嵌套路由守卫 当配合嵌套路由使用时,父路由和子路由都可以使用 beforeEnter。如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发。例如: ```js const routes = [ { path: '/user', beforeEnter() { // ... }, children: [ { path: 'list', component: UserList }, { path: 'details', component: UserDetails }, ], }, ] ``` 示例中的 beforeEnter 在 /user/list 和 /user/details 之间移动时不会被调用,因为它们共享相同的父级路由。如果我们直接将 beforeEnter 守卫放在 details 路由上,那么在这两个路由之间移动时就会被调用。 ## 组件内的守卫 这是 Vue Router 提供的三个可以在路由组件内部直接定义的钩子函数,专门用来处理与路由相关的组件生命周期事件 **官方文档通常列出三个:**`beforeRouteEnter`、`beforeRouteUpdate`、`beforeRouteLeave` **三个守卫分别做什么** |守卫|触发时机|典型用途| |--|--|--| |beforeRouteEnter|在路由进入该组件之前执行,此时组件实例还未创建|初始化数据、权限检查(但不能直接操作组件)| |beforeRouteUpdate|当同一组件被复用时执行(例如从 /user/1 到 /user/2)|响应路由参数变化,刷新组件数据| |beforeRouteLeave|当离开当前组件(切换到其他路由)之前执行|阻止未保存的修改丢失、清理定时器、取消请求| > 注意:beforeRouteUpdate 只在组件复用(/user/:id 这种动态路由参数变化,且组件被复用)时触发;如果跳转到完全不同类型的路由(例如从 /user/1 到 /about),不会触发,而是卸载当前组件。 <mark>**特别说明:组件内的三个守卫都不能卸载 script set 中**</mark> ### beforeRouteEnter beforeRouteEnter —— 进入组件前,实例未创建 ```javascript export default { data() { return { userData: null } }, beforeRouteEnter(to, from, next) { // ❌ 这里不能访问 this(因为组件实例还没创建) // console.log(this.userData) // undefined 或报错 // ✅ 唯一能做的事:调用 next 并传入一个回调,回调会在组件实例创建后执行 next(vm => { // vm 就是当前组件的实例(this) console.log(vm.userData) // 可以在这里调用组件的方法或修改 data vm.loadUserData(to.params.id) }) } } ``` **为什么这样设计?** 因为路由解析阶段需要尽早决定是否允许进入,如果允许,框架再创建组件实例。`next` 的回调给了你“等实例创建完再操作”的通道。 **常见使用场景**: - 检查权限,如果失败,`next(false)` 或重定向。 - 提前获取数据并存入 store,然后在回调中同步到组件。 **现代替代方案(Vue 3 组合式 API)**: 在 `setup` 中使用 `onBeforeRouteEnter`(注意它也接收一个回调,但无法直接获得组件实例,因为还没创建;不过你可以通过 `next` 回调获得实例,写法类似): ```javascript import { onBeforeRouteEnter } from 'vue-router' export default { setup() { onBeforeRouteEnter((to, from, next) => { next(vm => { // vm 是组件实例,在组合式 API 中可用 vm.someMethod() }) }) } } ``` 但实际开发中,**更推荐在 `setup` 中直接使用路由参数或监听路由变化**,不一定非要用守卫。比如用 `watch` 监听 `route.params`。 --- ### beforeRouteUpdate beforeRouteUpdate —— 组件复用时响应参数变化 考虑这个路由:`/user/:id`,组件 `UserProfile.vue`。 - 第一次进入 `/user/1` → 触发 `beforeRouteEnter` + 组件创建。 - 然后在同一个组件内点击链接跳转到 `/user/2` → 不会重新创建组件,只会**复用**已有组件实例。此时**没有** `beforeRouteEnter`,而是触发 **`beforeRouteUpdate`**。 ```javascript export default { async beforeRouteUpdate(to, from, next) { // ✅ 此时 this 可以访问当前组件实例 this.loading = true try { await this.fetchUserData(to.params.id) next() // 继续导航 } catch (error) { next(false) // 取消导航 } } } ``` **为什么需要它?** 如果不处理,组件复用时页面数据不会刷新,依然是老的 `/user/1` 的内容。你可以用 `watch` 监听 `$route` 来达到同样效果,但 `beforeRouteUpdate` 更语义化,并且可以**中断导航**(如果数据加载失败,可以阻止跳转)。 **Vue 3 组合式 API 写法**: ```javascript import { onBeforeRouteUpdate } from 'vue-router' export default { setup() { onBeforeRouteUpdate(async (to, from) => { // 没有 next,而是通过返回值控制 // 返回 false 取消导航 // 或者返回路由地址进行重定向 }) } } ``` ### beforeRouteLeave beforeRouteLeave —— 离开前阻止或清理 ```javascript export default { data() { return { formData: { name: '', content: '' }, saved: false } }, beforeRouteLeave(to, from, next) { if (!this.saved && (this.formData.name || this.formData.content)) { const answer = window.confirm('您有未保存的更改,确定要离开吗?') if (answer) { next() } else { next(false) // 取消离开 } } else { next() } }, beforeDestroy() { // 可以在这里清理定时器,但 beforeRouteLeave 更适合与用户交互相关的阻止 clearInterval(this.timer) } } ``` **与 beforeDestroy / unmounted 的区别**: - beforeRouteLeave 可以**阻止**离开(比如弹窗让用户确认)。 - beforeDestroy / unmounted 发生在组件已经被确定要销毁之后,无法阻止。 > **注意**:不要依赖 beforeRouteLeave 来做数据清理(如取消未完成的请求),因为如果用户直接关闭浏览器标签页,这个守卫不会执行。可靠的清理仍需放在 beforeDestroy / unmounted 中。 ### 常见困惑及解答 + 困惑1:为什么 beforeRouteEnter 里拿不到 this ?怎么传数据进去? - 原因:守卫执行在组件创建之前,this 尚未绑定。 - 解法:使用 `next(vm => { ... })` 回调,或者**放弃在 beforeRouteEnter 中操作组件**,改用 store 或全局状态。实际上很多场景可以用 watch 或直接在组件 created 里通过 $route 做同样的事。 + 困惑2:beforeRouteUpdate 和 watch $route 哪个好? - beforeRouteUpdate 的优点是**可以取消导航**(`next(false)`),适合依赖异步数据加载且失败时想停留在原页面的场景。 - watch $route 更简单直观,但不能取消导航(除非你在 watch 里再调用 router.push 等,但体验不好)。所以优先用 beforeRouteUpdate 当需要取消导航时;否则用 watch 更轻量。 + 困惑3:beforeRouteLeave 里调用 next(false) 是不是会完全阻止离开?可以重定向吗? - next(false) 阻止离开,页面停留在当前路由。 - 也可以 next('/login') 重定向到其他路由。 + 困惑4:在 Vue 3 组合式 API 中,onBeforeRouteLeave 和 onBeforeRouteUpdate 为什么没有 next 参数? - 这是 Vue Router 4 的改进:推荐使用返回值而不是 next,以避免回调地狱。例如: ```javascript onBeforeRouteLeave((to, from) => { if (hasUnsavedChanges) { return confirm('确定离开?') // 如果 confirm 返回 false,则取消导航 } }) ``` - 返回 `false` → 取消导航 - 返回路由路径 → 重定向 - 返回 `undefined` / `true` → 继续 --- ### 实战记忆技巧(脑图结构) ```bash 组件内守卫 ├── beforeRouteEnter —— 组件未创建时,拿不到 this,靠 next 回调 │ ├── 用途:权限预检、预加载数据但不操作 DOM │ └── 替代:在 created 里通过 $route 处理,除非需要阻止进入 ├── beforeRouteUpdate —— 同一组件参数变化时,有 this,可取消导航 │ ├── 用途:刷新数据、响应动态路由参数 │ └── 与 watch $route 的区别:可中断导航 └── beforeRouteLeave —— 离开前,有 this,可阻止或重定向 ├── 用途:未保存提醒、清理副作用 └── 注意:不能完全替代 beforeDestroy ```
扫一扫手机看
点击排行
windows 文件资源管理器使用
Layui 弹窗open使用说明
SQL Server 中字符串函数使用
C# 使用QQ邮箱代理做邮件接收功能
SQL Server关于apply的两种形式cross apply 和 outer apply
ASP.NET MVC 模型的自定义验证
nvm安装及常用命令
SQL Server 中数据类型转换
SQL Server 中WITH AS短语的使用
IE浏览器下AJAX缓存问题导致数据不更新的解决办法
订阅号