3.4路由守卫
在Vue 2中,Vue Router提供了路由守卫(Navigation Guards)的功能,这允许你在路由跳转之前或之后执行逻辑。这些守卫可以用来处理认证、授权、页面标题修改、记录分析数据等任务
路由守卫类型
-
全局守卫(Global Guards)
- beforeEach:每次路由跳转前调用。它接受三个参数:
to
(目标路由对象)、from
(当前路由对象)和next
(进行导航确认的方法)。 - beforeResolve:在路由被解析之后,导航被确认之前调用。它的使用方式与
beforeEach
相似。 - afterEach:在路由跳转完成后调用。这个钩子不会接收到
next
函数,因为它不需要控制导航。
- beforeEach:每次路由跳转前调用。它接受三个参数:
全局守卫:对所有路由生效的拦截逻辑
全局守卫注册后会作用于整个应用的所有路由跳转,包括前置守卫、解析守卫和后置守卫三种类型,其中前置守卫(beforeEach) 是实际开发中最常用的权限控制手段。
全局守卫的类型与触发时机
守卫类型 | 触发时机 | 参数说明 |
---|---|---|
beforeEach | 路由跳转前触发(所有路由共用) | to: 目标路由对象 from: 当前路由对象 next: 控制跳转的函数(Vue2中必填) |
beforeResolve | 在所有组件内守卫和异步路由组件解析后触发(较少使用) | 同beforeEach |
afterEach | 路由跳转完成后触发(无next参数,仅用于页面状态更新如滚动位置重置) | to: 目标路由对象 from: 当前路由对象 |
典型应用:全局登录状态验证
router.beforeEach((to, from, next) => { // 判断目标路由是否需要登录权限(通过路由元信息meta.requireAuth 标记) if (to.meta.requireAuth) { const token = sessionStorage.getItem('token'); if (token) { next(); // 已登录,放行 } else { next('/login'); // 未登录,重定向至登录页 } } else { next(); // 无需登录的路由直接放行 }
});
关键步骤:
综合案例:
- 在路由配置中通过
meta: { requireAuth: true }
标记需要权限的路由2; - 登录成功后将token存入
sessionStorage
(临时存储,关闭浏览器失效)或localStorage
(持久化存储)1; - 守卫中通过
to.meta.requireAuth
判断是否拦截,并验证token有效性。
创建路由配置并添加全局守卫
创建 router/index.js
文件,并设置路由及所有全局守卫:
import Vue from 'vue';
import Router from 'vue-router';
import Login from '@/components/Login.vue';
import Dashboard from '@/components/Dashboard.vue';
import ErrorPage from '@/components/ErrorPage.vue';Vue.use(Router);// 假设的登录状态管理函数
function isAuthenticated() {return localStorage.getItem('authToken') !== null;
}const router = new Router({routes: [{path: '/login',name: 'Login',component: Login},{path: '/dashboard',name: 'Dashboard',component: Dashboard,meta: { requiresAuth: true } // 添加元信息,标识需要认证},{path: '/error',name: 'Error',component: ErrorPage},{path: '*',redirect: '/login'}]
});// beforeEach 守卫:用于验证用户是否已登录
router.beforeEach((to, from, next) => {if (to.matched.some(record => record.meta.requiresAuth)) {if (!isAuthenticated()) {next({path: '/login',query: { redirect: to.fullPath }});} else {next();}} else {next(); // 确保一定要调用 next()}
});// beforeResolve 守卫:模拟数据加载完成前的操作
router.beforeResolve((to, from, next) => {console.log("准备完成所有可能的异步组件和钩子...");// 这里可以执行一些异步操作,比如数据预取等next();
});// afterEach 守卫:用于更新页面标题
router.afterEach((to, from) => {document.title = to.name || '默认标题'; // 根据路由名称或者自定义逻辑设置页面标题
});export default router;
创建组件
接下来,我们需要创建三个组件:Login.vue
、Dashboard.vue
和 ErrorPage.vue
。
Login.vue
<template><div><h2>登录</h2><button @click="login">登录</button></div>
</template><script>
export default {methods: {login() {// 设置登录状态localStorage.setItem('authToken', 'some-auth-token');const redirectTo = this.$route.query.redirect || '/dashboard';this.$router.push(redirectTo);}}
}
</script>
Dashboard.vue
<template><div><h2>欢迎来到仪表盘</h2><p>这是受保护的页面。</p><button @click="logout">退出登录</button></div>
</template><script>
export default {methods: {logout() {localStorage.removeItem('authToken');this.$router.push('/login');}}
}
</script>
ErrorPage.vue
<template><div><h2>404 - 页面未找到</h2></div>
</template>
在主应用中使用路由
最后,在你的主应用文件中(例如 main.js
),引入并使用路由器:
import Vue from 'vue';
import App from './App.vue';
import router from './router';new Vue({el: '#app',router,render: h => h(App)
});
通过这个例子,我们展示了如何在 Vue 2 应用程序中使用所有的全局守卫:beforeEach
、beforeResolve
和 afterEach
。这使得我们可以基于用户的登录状态控制路由访问,处理异步数据加载,并动态地更新页面标题。
组件内守卫:组件级别的路由拦截
组件内守卫仅作用于当前组件对应的路由,定义在组件选项中,适用于组件专属逻辑(如表单未保存提示、动态数据加载)。
组件内守卫的三种类型
组件内守卫仅作用于当前组件对应的路由,定义在组件选项中,适用于组件专属逻辑(如表单未保存提示、动态数据加载)。
- beforeRouteEnter:在渲染该组件的对应路由被确认前调用。你不能在这个守卫中访问
this
,因为当守卫执行时实例还未创建。路由进入前触发(此时组件实例未创建,无法通过this
访问组件),需通过next(vm => { ... })
回调获取组件实例:beforeRouteEnter(to, from, next) { next(vm => { // vm即当前组件实例,可调用组件方法或访问数据 vm.loadData(to.params.id); }); }
- beforeRouteUpdate:在当前路由改变,但是该路由被复用时调用。例如,对于一个带有动态参数的路径,在参数改变时,组件会被复用,此时可以使用此守卫来响应数据的变化。路由参数变化时触发(如从
/user/1
跳转到/user/2
,组件复用场景),可直接访问this
:beforeRouteUpdate(to, from, next) { this.userId = to.params.id; // 更新组件数据 next(); }
- beforeRouteLeave:在导航离开该组件的对应路由时调用。它可以用来阻止用户意外离开,避免未保存的数据丢失.路由离开前触发(常用于阻止未保存操作),通过
next(false)
阻止跳转:beforeRouteLeave(to, from, next) { if (this.formModified) { if (confirm('表单未保存,确定离开吗?')) { next(); } else { next(false); // 取消跳转 } } else { next(); } }
综合案例:
<template><div><h2>编辑用户信息</h2><input v-model="form.name" placeholder="姓名" /><input v-model="form.email" placeholder="邮箱" /><button @click="save">保存</button><button @click="$router.go(-1)">返回</button></div>
</template><script>
export default {name: 'UserEdit',data() {return {form: {name: '',email: ''},originalForm: {}, // 保存原始数据用于比较hasUnsavedChanges: false}},// 👇 组件内守卫:进入路由前beforeRouteEnter(to, from, next) {// 注意:此时组件实例还未创建,不能访问 `this`// 如果需要获取数据,建议通过 API 获取,并通过 `next` 传递给组件console.log('准备进入用户编辑页,来源路由:', from.path)// 模拟异步获取用户数据fetchUserById(to.params.id).then(userData => {next(vm => {// `vm` 是组件实例vm.form = { ...userData }vm.originalForm = { ...userData }})}).catch(() => {next(false) // 取消导航// 或者跳转到错误页// next('/error')})},// 👇 组件内守卫:路由更新时(例如 id 变化)beforeRouteUpdate(to, from, next) {console.log('路由参数更新,准备加载新用户数据')// 可以在此处重新获取数据if (this.hasUnsavedChanges) {const answer = window.confirm('你有未保存的更改,确定要切换用户吗?')if (!answer) {next(false)return}}// 获取新用户数据fetchUserById(to.params.id).then(userData => {this.form = { ...userData }this.originalForm = { ...userData }this.hasUnsavedChanges = falsenext()}).catch(() => {next(false)})},// 👇 组件内守卫:离开当前路由时beforeRouteLeave(to, from, next) {console.log('准备离开编辑页,目标路由:', to.path)// 判断是否有未保存的更改if (this.hasUnsavedChanges) {const answer = window.confirm('你的更改尚未保存,确定要离开吗?')if (answer) {next() // 允许离开} else {next(false) // 取消导航}} else {next() // 直接允许离开}},watch: {form: {handler() {// 监听表单变化,标记为有未保存更改this.hasUnsavedChanges = true},deep: true}},methods: {save() {// 模拟保存console.log('保存数据:', this.form)this.hasUnsavedChanges = falsealert('保存成功!')}}
}// 模拟 API 请求
function fetchUserById(id) {return new Promise(resolve => {setTimeout(() => {resolve({id,name: `用户${id}`,email: `user${id}@example.com`})}, 500)})
}
</script>
测试场景
- 进入
/user/1/edit
→ 触发beforeRouteEnter
,加载用户1数据。 - 修改表单但不保存 → 点击返回或跳转其他页面 → 弹出确认框。
- 点击“确定” → 允许跳转;点击“取消” → 停留在当前页。
- 在页面内跳转到
/user/2/edit
→ 触发beforeRouteUpdate
,提示是否切换。
独享守卫:路由配置中的独立拦截逻辑
beforeEnter:在路由跳转进入该路由之前调用。它定义于路由配置内,并且只影响该特定路由。
独享守卫通过在路由配置中定义beforeEnter
属性实现,仅对当前路由生效,优先级介于全局守卫和组件内守卫之间,适用于单一路由的特殊验证规则(如管理员权限校验)。
基础用法与执行顺序
- 定义方式:在路由规则中添加
beforeEnter
函数:const routes = [ { path: '/admin', component: Admin, beforeEnter: (to, from, next) => { const role = sessionStorage.getItem('role'); if (role === 'admin') { next(); } else { next('/403'); // 非管理员跳转至无权限页 } } } ];
- 执行顺序:全局
beforeEach
→ 独享beforeEnter
→ 组件内beforeRouteEnter
综合案例:
假设一个场景:只有管理员(userRole === 'admin'
)才能访问 /dashboard
路由,普通用户访问时跳转到 /login
并提示无权限
import Vue from 'vue'
import Router from 'vue-router'
import Dashboard from '@/views/Dashboard'
import Login from '@/views/Login'
import Home from '@/views/Home' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Home', component: Home }, { path: '/login', name: 'Login', component: Login }, { path: '/dashboard', name: 'Dashboard', component: Dashboard, // 独享守卫:仅对当前路由生效 beforeEnter: (to, from, next) => { // 模拟用户角色(实际项目中可能从 Vuex/本地存储获取) const userRole = localStorage.getItem('userRole') || 'user' // 默认普通用户 // 权限判断:只有 admin 可进入 if (userRole === 'admin') { next() // 允许进入当前路由 } else { alert('无权限访问!请先登录管理员账号') next('/login') // 普通用户强制跳转登录页 } } } ]
})
<template> <div> <h2>登录页</h2> <button @click="login('user')">普通用户登录</button> <button @click="login('admin')">管理员登录</button> </div>
</template> <script>
export default { methods: { login(role) { localStorage.setItem('userRole', role) // 存储用户角色 this.$router.push('/') // 登录后跳转首页 } }
}
</script>
测试效果
- 普通用户登录后,点击跳转到
/dashboard
,会触发beforeEnter
守卫,提示“无权限”并强制跳转/login
。 - 管理员登录后,点击跳转到
/dashboard
,守卫验证通过,正常进入页面。