Code前端首页关于Code前端联系我们

一、Vue2路由守卫有哪些核心类型?

terry 1天前 阅读数 19 #Vue
文章标签 Vue2;路由守卫

做Vue2项目时,路由跳转的权限控制、页面离开前的表单提醒、进入页面时的预加载数据……这些场景都绕不开「路由守卫」,但刚接触时,很多同学会懵:路由守卫分哪几类?不同场景该选哪种?实战里怎么避免踩坑?今天用问答形式把Vue2路由守卫的核心知识点和实战技巧讲透。

问:Vue2里路由守卫是怎么分类的?不同类型作用范围有啥区别? 答:Vue2的路由守卫主要分**全局守卫**、**路由独享守卫**、**组件内守卫**三类,作用范围和触发时机各有不同: - 「全局守卫」作用于整个应用的所有路由,像`router.beforeEach`(跳转前拦截)、`router.afterEach`(跳转后执行)这些,只要路由变化就会触发; - 「路由独享守卫」只作用于单个路由配置,在路由规则里用`beforeEnter`定义,只有访问这个路由时才触发; - 「组件内守卫」写在Vue组件的选项里,beforeRouteEnter`(进入组件前)、`beforeRouteUpdate`(路由参数变化但组件复用)、`beforeRouteLeave`(离开组件前),只对当前组件生效。

简单说,全局管所有路由,独享管单个路由,组件内管自己组件的生命周期+路由交互。

全局路由守卫怎么用?

问:全局守卫里最常用的beforeEachafterEach,实际项目中怎么落地?
答:全局守卫的核心是拦截所有路由跳转流程,适合做全局权限控制、页面统计这类通用逻辑。

先看router.beforeEach(全局前置守卫):它会在每次路由跳转前触发,接收to(目标路由)、from(当前路由)、next(放行/跳转函数)三个参数,典型场景是「登录态拦截」——比如后台管理系统,判断用户有没有登录:

router.beforeEach((to, from, next) => {
  const isLogin = localStorage.getItem('token')
  // 目标路由需要登录,但用户没登录 → 跳登录页
  if (to.meta.requiresAuth && !isLogin) {
    next('/login')
  } else {
    next() // 放行
  }
})

这里用了路由元信息meta,给需要权限的路由加标记(比如{ meta: { requiresAuth: true } }),灵活控制哪些页面要拦截。

再看router.afterEach(全局后置守卫):它在路由跳转完成后触发,没有next函数(因为跳转已经完成了),适合做「页面标题修改」「埋点统计」这类不影响跳转流程的操作:

router.afterEach((to, from) => {
  // 动态设置页面标题
  document.title = to.meta.title || '默认标题'
  // 统计页面访问次数
  reportPageView(to.path)
})

路由独享守卫适合哪些场景?

问:路由独享的beforeEnter,和全局、组件内守卫有啥不一样?啥时候用它?
答:beforeEnter写在单个路由的配置对象里,作用范围是「这一个路由」,比全局守卫更精准,比组件内守卫更通用(不用和组件耦合),适合「单个路由的特殊验证」场景。

举个例子:做付费课程平台时,某个课程详情页/course/:id,需要判断用户是否购买过该课程,如果每个课程都写在组件内守卫,代码会冗余;用全局守卫又太泛(不是所有页面都要验购买),这时候用beforeEnter就很合适:

const routes = [
  {
    path: '/course/:id',
    component: CourseDetail,
    beforeEnter: (to, from, next) => {
      const courseId = to.params.id
      const hasBought = checkBought(courseId) // 自定义验权函数
      if (hasBought) {
        next()
      } else {
        next('/buy?courseId=' + courseId) // 跳购买页
      }
    }
  }
]

这种「单个路由的专属逻辑」,用beforeEnter既不影响其他路由,又能和路由配置紧耦合,维护起来更清晰。

组件内守卫和其他守卫有啥区别?

问:组件里的beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave,各自啥时候触发?实际怎么用?
答:组件内守卫的核心是和「组件生命周期」联动,处理「组件和路由交互」的细节问题,三个守卫分工明确:

beforeRouteEnter:进入组件前触发

它在组件实例创建前执行(所以不能访问this,因为组件还没生成),但如果需要在进入时拿组件实例做操作,可以用next(vm => { ... })vm就是组件实例:

export default {
  beforeRouteEnter(to, from, next) {
    // 这里拿不到this → 因为组件还没创建
    next(vm => {
      // 组件创建后,给vm(组件实例)赋值
      vm.fetchData() // 调用组件内的方法加载数据
    })
  }
}

适合「进入组件前预加载数据」,而且需要用到组件实例的场景(比如调用组件内的方法)。

beforeRouteUpdate:路由参数变化时触发

当路由是「动态参数路由」(比如/user/:id),且组件被复用时(比如从/user/1跳到/user/2,组件不销毁,只是参数变了),这个守卫会触发,这时候可以访问this,因为组件已经存在:

export default {
  beforeRouteUpdate(to, from, next) {
    // 可以拿到this → 组件已存在
    this.userId = to.params.id // 更新组件内的userId
    this.fetchUserInfo() // 重新加载用户信息
    next()
  }
}

解决「路由参数变化但组件复用,数据没更新」的问题。

beforeRouteLeave:离开组件前触发

离开当前组件,跳转到其他路由前触发,能访问this,适合「表单未保存提醒」「页面状态清理」这类场景:

export default {
  beforeRouteLeave(to, from, next) {
    if (this.formChanged) { // 假设formChanged标记表单是否修改
      const confirm = window.confirm('表单还没保存,确定离开吗?')
      if (confirm) {
        next() // 确认离开 → 放行
      } else {
        next(false) // 取消离开 → 留在当前页
      }
    } else {
      next() // 表单没修改 → 直接放行
    }
  }
}

实战中怎么结合路由守卫做权限管理?

问:后台系统里不同角色(比如admin、editor)能访问的页面不一样,怎么用路由守卫实现细粒度权限控制?
答:权限管理的核心思路是「路由元信息+全局守卫+动态路由」结合,分三步走:

给路由加「权限标记」(用meta)

在路由配置里,给需要权限的页面加meta.role,标记允许访问的角色:

const routes = [
  { path: '/admin', component: AdminPage, meta: { role: 'admin' } },
  { path: '/editor', component: EditorPage, meta: { role: 'editor' } },
  { path: '/login', component: LoginPage }
]

全局守卫拦截权限(用beforeEach)

router.beforeEach里,判断用户角色是否匹配路由要求:

router.beforeEach((to, from, next) => {
  const userRole = localStorage.getItem('role') // 假设存在localStorage
  // 目标路由需要权限,且用户角色不匹配 → 跳403或登录
  if (to.meta.role && to.meta.role !== userRole) {
    next('/403') // 没有权限的页面
  } else {
    next()
  }
})

动态加载路由(应对后端返回权限的场景)

如果权限是后端返回的(比如不同角色能访问的菜单不同),需要用router.addRoute动态添加路由,同时在守卫里处理:

// 假设后端返回可访问的路由列表
const asyncRoutes = request.get('/api/routes') 
asyncRoutes.forEach(route => {
  router.addRoute(route) // 动态添加路由
})
// 全局守卫里还要处理“动态路由加载后,新路由的权限”
router.beforeEach((to, from, next) => {
  // 先判断是否是动态路由,再做权限拦截(逻辑类似上面)
  // ...
})

这样不管是静态路由还是动态路由,权限都能被全局守卫拦截,实现细粒度控制。

路由守卫里的next函数要注意什么?

问:写路由守卫时,next函数经常用错导致页面卡死或逻辑混乱,有哪些关键细节要注意?
答:next是控制路由跳转的「开关」,但用法不对很容易出问题,核心注意这三点:

next只能调用一次

路由守卫里next调用多次会导致逻辑冲突(比如既放行又跳转),尤其是用async/await时,要确保next只执行一次:

router.beforeEach(async (to, from, next) => {
  const isLogin = await checkLogin() // 异步验权
  if (isLogin) {
    next() // 只调用一次
  } else {
    next('/login') // 只调用一次
  }
})

next的不同用法

  • next():正常放行,进入下一个守卫或跳转;
  • next('/path'):强制跳转到指定路径(会触发新的路由守卫流程);
  • next(false):取消跳转,留在当前页面;
  • next(error):传递错误(比如next(new Error('权限不足'))),会触发全局错误处理router.onError

避免在next里写异步逻辑后忘记调用

比如异步请求后,一定要调用next,否则路由会一直处于「等待跳转」状态,页面卡死:

// 错误示例:忘记调next
beforeRouteEnter(to, from, next) {
  fetchData().then(() => {
    // 这里没调next → 路由一直卡着
  })
}
// 正确示例:请求后调next
beforeRouteEnter(to, from, next) {
  fetchData().then(() => {
    next() // 记得调!
  })
}

多个路由守卫的执行顺序是怎样的?

问:全局、独享、组件内守卫同时存在时,执行顺序是啥样的?搞不清顺序容易埋坑…
答:路由守卫的执行顺序是「全局前置 → 路由独享 → 组件内 → 全局解析 → 全局后置」,具体流程:

  1. 触发router.beforeEach(全局前置守卫,可多个,按注册顺序执行);
  2. 触发路由配置里的beforeEnter(路由独享守卫);
  3. 触发组件内的beforeRouteEnter(组件内守卫);
  4. 解析异步组件(如果有);
  5. 触发router.beforeResolve(全局解析守卫,所有守卫完成后,导航确认前触发);
  6. 导航确认,页面跳转;
  7. 触发router.afterEach(全局后置守卫);
  8. 触发组件内的activated等生命周期(如果是keep-alive组件)。

举个例子:访问带beforeEnter的路由,同时组件内有beforeRouteEnter,全局有beforeEach,执行顺序是beforeEachbeforeEnterbeforeRouteEnterbeforeResolveafterEach

理解这个顺序,才能在复杂场景下(比如多个守卫配合处理权限、数据)不搞混逻辑。

路由守卫能处理数据预加载吗?

问:进入页面时需要先加载数据再渲染,路由守卫怎么配合做数据预加载?
答:数据预加载有两种思路,「守卫里加载数据」「组件内加载数据」,路由守卫在其中起「拦截等待数据」的作用:

全局/独享守卫里加载数据

适合「通用数据」(比如所有页面都要的用户信息),在beforeEachbeforeEnter里发起请求,拿到数据后再放行:

router.beforeEach(async (to, from, next) => {
  if (!userInfo) { // 假设userInfo是全局数据
    userInfo = await fetchUserInfo() // 加载用户信息
  }
  next()
})

组件内守卫里加载数据

适合「组件专属数据」,用beforeRouteEnternext(vm => {}),等组件创建后再调用方法:

export default {
  data() { return { list: [] } },
  beforeRouteEnter(to, from, next) {
    fetchList().then(data => {
      next(vm => {
        vm.list = data // 给组件实例的list赋值
      })
    })
  }
}

或者在beforeRouteEnter里直接把数据传给组件(如果不需要组件实例):

beforeRouteEnter(to, from, next) {
  fetchList().then(data => {
    next({ ...to, params: { ...to.params, data } }) // 把数据通过路由参数传
  })
}

路由守卫和Vuex怎么配合?

问:Vuex存了用户状态、权限等数据,路由守卫怎么和Vuex联动?
答:Vuex是状态管理中心,路由守卫可以「读Vuex状态」做判断,也可以「写Vuex」触发状态更新,常见场景:

读Vuex:权限判断

全局守卫里从Vuex取用户角色,判断是否能访问目标路由:

import store from './store'
router.beforeEach((to, from, next) => {
  const userRole = store.state.user.role
  if (to.meta.role && to.meta.role !== userRole) {
    next('/403')
  } else {
    next()
  }
})

写Vuex:数据更新/保存

组件内守卫里,把临时数据提交到Vuex(比如离开页面时保存表单草稿):

export default {
  beforeRouteLeave(to, from, next) {
    if (this.formData) {
      this.$store.commit('saveDraft', this.formData) // 保存到Vuex
    }
    next()
  }
}

异步Action和守卫结合

async/await调用Vuex的Action,等数据加载完再放行:

router.beforeEach(async (to, from, next) => {
  await store.dispatch('fetchUserInfo') // 调用Vuex的Action加载用户信息
  next()
})

开发中遇到路由守卫不生效怎么排查?

问:写了路由守卫但没触发,页面跳转没拦截,怎么找问题?
答:路由守卫不生效,通常是「注册位置不对」「next没调」「路由模式影响」这三类原因,排查步骤:

检查守卫注册位置

  • 全局守卫(router.beforeEach等)要写在new VueRouter()之后,且确保router实例被正确导入;
  • 路由独享守卫(beforeEnter)要写在路由配置对象里,
    const routes = [
      { path: '/xxx', component: Xxx, beforeEnter: (to, from, next) => { ... } }
    ]
  • 组件内守卫要写在Vue组件的选项里(不是methods里),
    export default {
      beforeRouteEnter() { ... }, // 正确
      methods: { beforeRouteEnter() { ... } } // 错误!
    }

检查next是否正确调用

如果守卫里没调next(),路由会一直处于「等待跳转」状态,导致守卫看似没生效,比如异步函数里忘记调next:

// 错误:异步后没调next
beforeEach((to, from, next) => {
  fetchData().then(() => {
    // 这里没调next → 路由不跳转
  })
})
// 正确:异步后调next
beforeEach((to, from, next) => {
  fetchData().then(() => {
    next()
  })
})

检查路由模式和异步组件

  • 路由模式是hash还是history?如果是history,要确保服务端配置了 fallback,否则刷新页面可能导致路由守卫不触发;
  • 路由是异步组件(比如component: () => import('./Xxx.vue')),要确保异步组件加载完成后,守卫逻辑能

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门