在 Vue 单页应用(SPA)开发中,vue-router是实现路由跳转和页面导航的核心工具。而 “路由守卫” 作为vue-router的重要功能,相当于路由跳转过程中的 “拦截器”—— 它能在路由跳转前、跳转中、跳转后执行自定义逻辑,实现权限控制、页面拦截、数据预加载等关键需求。

本文将从 “路由守卫的分类”“各类守卫的用法与执行时机”“实际应用场景” 三个维度,带你彻底掌握 vue-router 路由守卫的核心知识点,解决开发中常见的导航控制问题。

一、路由守卫的核心概念与分类

路由守卫的本质是 “导航钩子函数”,即在路由导航的不同阶段触发的回调函数。根据 “作用范围” 和 “执行时机”,vue-router 的路由守卫可分为三大类:

  1. 全局守卫:作用于整个应用的所有路由,如全局前置守卫、全局解析守卫、全局后置钩子;

  2. 路由独享守卫:仅作用于某一个具体的路由规则,如beforeEnter

  3. 组件内守卫:作用于路由对应的组件内部,如组件内前置守卫、组件内更新守卫、组件内后置守卫。

不同守卫的执行时机有严格的顺序,理解这个顺序是正确使用守卫的关键(后文会详细拆解执行流程)。

二、全局守卫:控制整个应用的路由导航

全局守卫需要通过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参数,因为导航已经完成,无法再控制导航行为;

  • 仅接收tofrom参数,用于获取跳转前后的路由信息。

三、路由独享守卫:控制单个路由的导航

路由独享守卫仅作用于某一个具体的路由规则,在路由配置中通过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一致(tofromnext),用法相同。

四、组件内守卫:控制组件内部的路由导航

组件内守卫作用于路由对应的组件内部,通过在组件中定义特定的钩子函数实现,可用于 “组件内的导航控制”(如判断组件是否有未保存的表单数据,阻止用户跳转)。

组件内守卫分为三类:beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave

1. beforeRouteEnter:组件进入前触发

作用:在路由即将进入组件前触发,此时组件实例还未创建(thisundefined),可用于 “预加载组件所需的数据”。

用法
<!-- 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>
关键特点
  • 组件实例未创建,thisundefined,需通过next(vm => { ... })访问组件实例;

  • 执行时机:路由独享beforeEnterbeforeRouteEnter → 组件创建 → 导航完成。

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/:idid变化。

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)时,完整的执行流程如下:

  1. 触发全局前置守卫(beforeEach);

  2. 触发目标路由的路由独享守卫(beforeEnter);

  3. 解析目标路由对应的组件(加载异步组件);

  4. 触发目标组件的beforeRouteEnter守卫;

  5. 触发全局解析守卫(beforeResolve);

  6. 导航完成,组件实例创建(createdmounted钩子执行);

  7. 触发全局后置钩子(afterEach)。

若导航被拦截(如next(false)next('/login')),则后续的守卫和钩子不会执行。

六、路由守卫的常见应用场景

  1. 登录权限控制:用beforeEach拦截未登录用户访问需要权限的页面(如个人中心、管理后台);

  2. 页面标题动态修改:用afterEach根据to.meta.title修改document.title

  3. 数据预加载:用beforeRouteEnter在组件进入前加载所需数据,减少页面加载时间;

  4. 未保存操作拦截:用beforeRouteLeave阻止用户未保存表单时跳转;

  5. 角色权限控制:用路由独享beforeEnter判断用户角色(如管理员 / 普通用户),控制是否允许访问特定路由;

  6. 全局 loading 管理:用beforeEach显示 loading,用afterEach关闭 loading,优化用户体验。

七、使用路由守卫的注意事项

  1. next必须调用:全局前置守卫(beforeEach)、路由独享守卫(beforeEnter)、组件内守卫(beforeRouteEnter/beforeRouteUpdate/beforeRouteLeave)中,next必须调用,否则导航会 “卡住”;

  2. beforeRouteEnterthisundefined:如需访问组件实例,需通过next(vm => { ... })

  3. 避免无限循环:不要在beforeEach中直接调用router.push,应使用next控制导航(如next('/login')而非router.push('/login'));

  4. 异步操作需谨慎:若在守卫中执行异步操作(如 API 请求),需确保next在异步操作完成后调用(否则会提前放行);

  5. meta字段合理使用:用to.meta存储路由的自定义信息(如requiresAuthtitle),使守卫逻辑更灵活可维护。

八、总结

路由守卫是vue-router中实现 “导航控制” 的核心工具,通过全局、路由独享、组件内三类守卫的组合,可满足从 “全局权限控制” 到 “组件内细节拦截” 的各类需求。

核心要点回顾:

  • 全局守卫:作用于所有路由,beforeEach用于登录拦截,afterEach用于跳转后收尾;

  • 路由独享守卫:作用于单个路由,beforeEnter用于特殊路由的权限控制;

  • 组件内守卫:作用于组件内部,`

更多推荐