深入理解 vue-router 路由守卫:分类、用法与实战
在 Vue 单页应用(SPA)开发中,vue-router是实现路由跳转和页面导航的核心工具。而 “路由守卫” 作为vue-router的重要功能,相当于路由跳转过程中的 “拦截器”—— 它能在路由跳转前、跳转中、跳转后执行自定义逻辑,实现权限控制、页面拦截、数据预加载等关键需求。
本文将从 “路由守卫的分类”“各类守卫的用法与执行时机”“实际应用场景” 三个维度,带你彻底掌握 vue-router 路由守卫的核心知识点,解决开发中常见的导航控制问题。
一、路由守卫的核心概念与分类
路由守卫的本质是 “导航钩子函数”,即在路由导航的不同阶段触发的回调函数。根据 “作用范围” 和 “执行时机”,vue-router 的路由守卫可分为三大类:
-
全局守卫:作用于整个应用的所有路由,如全局前置守卫、全局解析守卫、全局后置钩子;
-
路由独享守卫:仅作用于某一个具体的路由规则,如
beforeEnter; -
组件内守卫:作用于路由对应的组件内部,如组件内前置守卫、组件内更新守卫、组件内后置守卫。
不同守卫的执行时机有严格的顺序,理解这个顺序是正确使用守卫的关键(后文会详细拆解执行流程)。
二、全局守卫:控制整个应用的路由导航
全局守卫需要通过vue-router的实例(如router)进行注册,作用于所有路由的导航过程,常用于全局权限控制(如登录验证)、全局 loading 状态管理等场景。
1. 全局前置守卫(beforeEach):路由跳转前拦截
作用:在路由即将跳转前触发(即导航开始时),是最常用的全局守卫,可用于 “拦截未登录用户访问需要权限的页面”。
用法
// router/index.js(vue-router实例配置)
import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/views/Home';
import Profile from '@/views/Profile'; // 需要登录才能访问的页面
Vue.use(Router);
const router = new Router({
routes: [
{ path: '/', name: 'Home', component: Home },
{ path: '/profile', name: 'Profile', component: Profile, meta: { requiresAuth: true } } // meta字段标记需要权限
]
});
// 注册全局前置守卫
router.beforeEach((to, from, next) => {
// to:即将跳转到的目标路由对象(包含path、name、meta等信息)
// from:当前正要离开的路由对象
// next:必须调用的“导航控制函数”,决定下一步导航行为
// 示例:判断目标路由是否需要登录权限
if (to.meta.requiresAuth) {
// 检查本地存储中是否有登录状态(如token)
const token = localStorage.getItem('token');
if (token) {
// 已登录,允许跳转到目标路由
next();
} else {
// 未登录,拦截导航,强制跳转到登录页(并携带“当前路径”作为参数,方便登录后返回)
next({ path: '/login', query: { redirect: to.fullPath } });
}
} else {
// 不需要权限的路由,直接放行
next();
}
});
export default router;
关键参数解析
-
to:目标路由对象,包含路由的所有配置信息(如
to.path是目标路径,to.meta是路由的自定义元数据); -
from:当前路由对象,即跳转前所在的路由;
-
next:导航控制函数,必须调用,否则导航会 “卡住”,常见用法:
-
next():允许导航继续,跳转到to对应的路由; -
next(false):取消当前导航,停留在from对应的路由; -
next('/path')或next({ path: '/path' }):强制跳转到指定路由(如未登录时跳转到登录页); -
next(error):传递错误对象,会触发全局的onError钩子(Vue 2.4+)。
-
注意事项
-
next必须调用,且只能调用一次(多次调用会导致错误); -
不要在
beforeEach中使用router.push,否则会触发无限循环(应使用next控制导航)。
2. 全局解析守卫(beforeResolve):路由解析前触发
作用:在路由即将解析组件前触发,执行时机晚于beforeEach,但早于 “组件内前置守卫”。与beforeEach的区别是:beforeResolve会等待所有 “异步路由组件” 加载完成后再执行。
用法
// 注册全局解析守卫
router.beforeResolve((to, from, next) => {
// 场景:等待异步组件加载完成后,再进行权限判断
if (to.name === 'AsyncPage') {
// 假设AsyncPage是异步组件,这里可做加载完成后的逻辑(如检查数据)
console.log('异步组件即将解析完成');
}
next(); // 必须调用next
});
执行时机
beforeEach → 路由匹配的组件加载(异步组件) → beforeResolve → 组件内守卫 → 导航完成。
3. 全局后置钩子(afterEach):路由跳转后触发
作用:在路由跳转完成后触发(即页面已经渲染完成),无next参数,仅用于 “跳转后的收尾工作”(如修改页面标题、关闭 loading 状态)。
用法
// 注册全局后置钩子
router.afterEach((to, from) => {
// 示例1:根据目标路由的meta信息修改页面标题
if (to.meta.title) {
document.title = to.meta.title; // 如“个人中心 - 我的App”
}
// 示例2:关闭全局loading状态(假设页面跳转时显示loading)
const loading = document.getElementById('global-loading');
if (loading) loading.style.display = 'none';
});
关键特点
-
无
next参数,因为导航已经完成,无法再控制导航行为; -
仅接收
to和from参数,用于获取跳转前后的路由信息。
三、路由独享守卫:控制单个路由的导航
路由独享守卫仅作用于某一个具体的路由规则,在路由配置中通过beforeEnter字段定义,优先级高于全局守卫(但执行时机晚于beforeEach),适合 “单个路由的特殊权限控制”。
用法
// router/index.js
const router = new Router({
routes: [
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin'), // 异步加载组件
meta: { requiresAdmin: true }, // 标记需要管理员权限
// 路由独享守卫:仅作用于/admin路由
beforeEnter: (to, from, next) => {
// 示例:判断当前用户是否为管理员
const userRole = localStorage.getItem('userRole'); // 假设存储的角色是"admin"或"user"
if (userRole === 'admin') {
next(); // 管理员,允许访问
} else {
next('/403'); // 非管理员,跳转到403页面(无权限)
}
}
}
]
});
执行时机
全局beforeEach → 路由独享beforeEnter → 组件内守卫 → 导航完成。
注意事项
-
路由独享守卫仅对当前路由生效,不影响其他路由;
-
参数与
beforeEach一致(to、from、next),用法相同。
四、组件内守卫:控制组件内部的路由导航
组件内守卫作用于路由对应的组件内部,通过在组件中定义特定的钩子函数实现,可用于 “组件内的导航控制”(如判断组件是否有未保存的表单数据,阻止用户跳转)。
组件内守卫分为三类:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。
1. beforeRouteEnter:组件进入前触发
作用:在路由即将进入组件前触发,此时组件实例还未创建(this为undefined),可用于 “预加载组件所需的数据”。
用法
<!-- views/Profile.vue(需要登录的个人中心组件) -->
<template>
<div>
<h1>个人中心</h1>
<p>{{ userInfo.name }}</p>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: {} // 存储用户信息
};
},
// 组件内前置守卫:进入组件前触发
beforeRouteEnter(to, from, next) {
// 注意:此时this为undefined,无法直接访问组件实例
// 如需访问组件实例,可通过next的回调函数(Vue 2.2+支持)
next(vm => {
// vm:组件实例(相当于this)
// 示例:预加载用户信息(调用组件的方法)
vm.fetchUserInfo();
});
},
methods: {
fetchUserInfo() {
// 调用API获取用户信息(如从localStorage或后端接口)
const userInfo = JSON.parse(localStorage.getItem('userInfo'));
this.userInfo = userInfo;
}
}
};
</script>
关键特点
-
组件实例未创建,
this为undefined,需通过next(vm => { ... })访问组件实例; -
执行时机:路由独享
beforeEnter→beforeRouteEnter→ 组件创建 → 导航完成。
2. beforeRouteUpdate:组件复用(路由参数变化)时触发
作用:当组件被复用(即路由路径变化但组件不变,如/user/1跳转到/user/2)时触发,可用于 “更新组件内依赖路由参数的数据”。
场景说明
假设我们有一个 “用户详情页” 组件(UserDetail),路由配置为/user/:id—— 当从 “用户 1”(/user/1)跳转到 “用户 2”(/user/2)时,组件UserDetail会被复用(无需重新创建),此时created钩子不会触发,但beforeRouteUpdate会触发。
用法
<!-- views/UserDetail.vue -->
<template>
<div>
<h1>用户详情</h1>
<p>用户ID:{{ userId }}</p>
<p>用户名:{{ userName }}</p>
</div>
</template>
<script>
export default {
data() {
return {
userId: '',
userName: ''
};
},
created() {
// 组件首次创建时加载数据(仅执行一次)
this.loadUserData(this.$route.params.id);
},
// 组件复用(路由参数变化)时触发
beforeRouteUpdate(to, from, next) {
// 此时this已存在(组件已创建),可直接访问实例
// 更新userId(新路由的参数)
this.userId = to.params.id;
// 重新加载新用户的数据
this.loadUserData(to.params.id);
next(); // 允许导航继续
},
methods: {
loadUserData(userId) {
// 模拟调用API获取用户数据
setTimeout(() => {
this.userName = `用户${userId}`;
}, 500);
}
}
};
</script>
关键特点
-
组件已创建,
this可正常使用; -
仅在 “路由参数变化但组件复用” 时触发,如
/user/:id的id变化。
3. beforeRouteLeave:组件离开前触发
作用:在路由即将离开组件前触发,可用于 “拦截用户未保存的操作”(如表单填写一半,阻止用户跳转并提示保存)。
用法
<!-- views/EditForm.vue(表单编辑组件) -->
<template>
<div>
<h1>编辑表单</h1>
<input v-model="formContent" placeholder="请输入内容">
<button @click="$router.push('/home')">返回首页</button>
</div>
</template>
<script>
export default {
data() {
return {
formContent: '',
isFormChanged: false // 标记表单是否被修改
};
},
watch: {
// 监听表单内容变化,更新isFormChanged状态
formContent(newVal, oldVal) {
this.isFormChanged = newVal !== oldVal;
}
},
// 组件离开前触发
beforeRouteLeave(to, from, next) {
// 若表单已修改且未保存,提示用户
if (this.isFormChanged) {
const confirmLeave = window.confirm('表单内容未保存,确定要离开吗?');
if (confirmLeave) {
// 用户确认离开,允许导航
next();
} else {
// 用户取消离开,阻止导航
next(false);
}
} else {
// 表单未修改,直接放行
next();
}
}
};
</script>
关键特点
-
组件未销毁,
this可正常使用; -
常用于 “防止用户误操作导致数据丢失” 的场景。
五、路由守卫的执行顺序(完整导航流程)
掌握路由守卫的执行顺序,是避免逻辑冲突的关键。当触发一次路由导航(如从/home跳转到/profile)时,完整的执行流程如下:
-
触发全局前置守卫(
beforeEach); -
触发目标路由的路由独享守卫(
beforeEnter); -
解析目标路由对应的组件(加载异步组件);
-
触发目标组件的
beforeRouteEnter守卫; -
触发全局解析守卫(
beforeResolve); -
导航完成,组件实例创建(
created、mounted钩子执行); -
触发全局后置钩子(
afterEach)。
若导航被拦截(如next(false)或next('/login')),则后续的守卫和钩子不会执行。
六、路由守卫的常见应用场景
-
登录权限控制:用
beforeEach拦截未登录用户访问需要权限的页面(如个人中心、管理后台); -
页面标题动态修改:用
afterEach根据to.meta.title修改document.title; -
数据预加载:用
beforeRouteEnter在组件进入前加载所需数据,减少页面加载时间; -
未保存操作拦截:用
beforeRouteLeave阻止用户未保存表单时跳转; -
角色权限控制:用路由独享
beforeEnter判断用户角色(如管理员 / 普通用户),控制是否允许访问特定路由; -
全局 loading 管理:用
beforeEach显示 loading,用afterEach关闭 loading,优化用户体验。
七、使用路由守卫的注意事项
-
next必须调用:全局前置守卫(beforeEach)、路由独享守卫(beforeEnter)、组件内守卫(beforeRouteEnter/beforeRouteUpdate/beforeRouteLeave)中,next必须调用,否则导航会 “卡住”; -
beforeRouteEnter中this为undefined:如需访问组件实例,需通过next(vm => { ... }); -
避免无限循环:不要在
beforeEach中直接调用router.push,应使用next控制导航(如next('/login')而非router.push('/login')); -
异步操作需谨慎:若在守卫中执行异步操作(如 API 请求),需确保
next在异步操作完成后调用(否则会提前放行); -
meta字段合理使用:用to.meta存储路由的自定义信息(如requiresAuth、title),使守卫逻辑更灵活可维护。
八、总结
路由守卫是vue-router中实现 “导航控制” 的核心工具,通过全局、路由独享、组件内三类守卫的组合,可满足从 “全局权限控制” 到 “组件内细节拦截” 的各类需求。
核心要点回顾:
-
全局守卫:作用于所有路由,
beforeEach用于登录拦截,afterEach用于跳转后收尾; -
路由独享守卫:作用于单个路由,
beforeEnter用于特殊路由的权限控制; -
组件内守卫:作用于组件内部,`
更多推荐


所有评论(0)