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

一、vue router guard 到底是什么?

terry 3小时前 阅读数 9 #Vue

咱做Vue项目时,经常要控制页面能不能进、跳转前做些操作,这时候vue - router guard(路由导航守卫)就派上大用场啦!可不少刚接触的同学会疑惑:它到底是啥?不同类型咋用?权限管理咋结合?今天就用问答形式把这些事儿唠明白~

简单说,路由导航守卫就是路由跳转过程中的「钩子函数」,能在页面跳转的不同阶段(比如准备跳、跳的过程中、跳完之后),让咱插入自定义逻辑,举个生活里的例子:你进小区大门,门卫(守卫)会先查你有没有门禁卡(权限判断),允许进了再放行;或者进门前让你登记访客信息(数据处理),在Vue项目里,跳转页面、权限控制、加载数据这些常见需求,全靠它来实现~

路由跳转不是“嗖”一下直接到目标页,而是有个流程:导航被触发 → 在失活的组件里调用 leave 守卫 → 调用全局的 beforeEach 守卫 → 调用路由独享的 beforeEnter → 在激活的组件里调用 update 守卫(如果是组件复用情况)→ 调用 beforeRouteEnter(组件内守卫)→ 调用全局的 beforeResolve → 导航被确认 → 调用全局的 afterEach 守卫 → 触发 DOM 更新 → 调用 beforeRouteEnter 守卫里 next 的回调函数,创建好组件实例,守卫就是插在这些环节里的“操作入口”。

vue - router guard 有哪些类型?各自咋用?

vue - router 的守卫分三大类:全局守卫(作用于整个路由系统)、路由独享守卫(只作用于某一个路由)、组件内守卫(写在Vue组件里,针对当前组件的路由跳转),下面逐个拆解~

全局守卫:整个路由系统的“总闸”

全局守卫绑定在 router 实例上,不管跳哪个路由,都会触发,常见的有 router.beforeEachrouter.beforeResolverouter.afterEach 这三个,作用和时机各有不同。

① router.beforeEach(全局前置守卫):跳转流程里最先触发的全局守卫,能直接控制“是否允许跳转”,它接收三个参数:to(即将进入的目标路由对象)、from(当前要离开的路由对象)、next(放行/拦截的函数,必须调用才能继续导航)。

举个登录拦截的例子:后台系统里,部分页面需要登录才能进,咱可以用 beforeEach 全局判断登录状态:

// router/index.js
const router = createRouter({ ... })
router.beforeEach((to, from, next) => {
  // 假设用localStorage存登录标识
  const isLogin = localStorage.getItem('token') 
  // 路由元信息meta.requiresAuth标记该页面是否需要登录
  if (to.meta.requiresAuth) { 
    if (isLogin) {
      next() // 已登录,放行
    } else {
      next('/login') // 没登录,跳登录页
    }
  } else {
    next() // 不需要登录的页面,直接放行
  }
})

这样所有需要登录的页面,都会被这个“总闸”拦下检查,权限控制一步到位~

② router.beforeResolve(全局解析守卫):触发时机在 beforeEach 之后,组件内守卫 beforeRouteEnter 之前,它和 beforeEach 最大的区别是:beforeResolve 会等“所有路由组件内守卫、异步路由组件加载完成”后才触发,适合做统一的解析操作(比如处理完所有异步数据后再跳转)。

举个场景:多个异步路由组件加载时,想等它们都加载完再处理逻辑,就可以用 beforeResolve,不过日常开发里,它的出场率比 beforeEach 低些,大家重点记住触发时机和适用场景~

③ router.afterEach(全局后置守卫):路由跳转完成后触发,没有 next 函数(因为导航已经完成了),它主要用来做页面埋点、修改文档标题这类“跳转后操作”。

比如根据路由元信息改页面标题:

router.afterEach((to, from) => {
  // 假设路由配置里meta.title存了页面标题
  document.title = to.meta.title || '默认标题'
})

路由独享守卫:单个路由的“专属门卫”

路由独享守卫叫 beforeEnter,写在路由规则里,只对当前路由生效,触发时机在全局 beforeEach 之后,组件内守卫之前,适合给单个路由加专属逻辑。

举个订单详情页加载数据的例子:进入订单详情页前,得先请求订单数据,否则页面会空着,用 beforeEnter 就能实现“数据加载完再放行”:

const routes = [
  {
    path: '/order/:id',
    component: OrderDetail,
    // 路由独享守卫
    beforeEnter: (to, from, next) => {
      const orderId = to.params.id // 从路由参数拿订单ID
      // 假设fetchOrderDetail是请求订单数据的函数
      fetchOrderDetail(orderId).then(res => {
        // 把数据存到Vuex或组件里(这里用Vuex举例)
        store.commit('order/setDetail', res.data)
        next() // 数据存好后,放行进入页面
      }).catch(err => {
        console.log('订单数据加载失败', err)
        next('/404') // 加载失败跳404页面
      })
    }
  }
]

这样进订单页时,数据已经备好,页面渲染就不会空着啦~

组件内守卫:当前组件的“贴身管家”

组件内守卫是写在 Vue 组件 里的钩子,针对当前组件的路由跳转,有三个:beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave,各自管不同阶段。

① beforeRouteEnter:组件创建前触发:此时组件实例(this)还没生成,所以想访问组件实例,得在 next 的回调里操作,适合组件加载前初始化数据

举个页面加载前请求数据的例子:

<template>
  <div>{{ userInfo.name }}</div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {}
    }
  },
  beforeRouteEnter(to, from, next) {
    // 此时this是undefined,因为组件还没创建
    fetchUserInfo(to.params.userId).then(res => {
      // next的回调里,vm就是组件实例
      next(vm => {
        vm.userInfo = res.data // 给组件数据赋值
      })
    })
  }
}
</script>

② beforeRouteUpdate:组件复用时触发:当路由参数变化,但组件没销毁(比如从 /user/1 跳到 /user/2,用户组件复用)时,这个守卫会触发,适合处理路由参数变化后的逻辑

比如用户ID变了,重新加载用户信息:

export default {
  beforeRouteUpdate(to, from, next) {
    const newUserId = to.params.id
    this.fetchUser(newUserId) // 组件里的fetchUser方法,重新请求数据
    next() // 放行
  },
  methods: {
    fetchUser(userId) {
      // 请求用户数据...
    }
  }
}

③ beforeRouteLeave:离开组件时触发:跳转前,想拦截用户(比如表单没保存提醒),就靠它。

举个表单未保存提醒的例子:

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

怎么用路由守卫做权限管理?

后台管理系统里,不同角色(admin、editor)能访问的页面不一样,这时候路由守卫 + 路由元信息(meta)黄金搭档”,核心思路是:给路由加meta标记权限 → 全局守卫判断用户角色和meta是否匹配

步骤1:给路由配置meta字段

在路由规则里,用 meta.role 标记该页面需要的角色:

const routes = [
  // 管理员才能进的仪表盘
  { 
    path: '/admin/dashboard', 
    component: AdminDashboard, 
    meta: { role: 'admin' } 
  },
  // 编辑才能进的文章页
  { 
    path: '/editor/article', 
    component: EditorArticle, 
    meta: { role: 'editor' } 
  },
  // 登录页,不需要权限
  { path: '/login', component: Login }
]

步骤2:全局守卫判断角色

router.beforeEach 全局拦截,判断用户角色是否符合路由要求,假设用户角色存在Vuex的 store.state.user.role 里:

router.beforeEach((to, from, next) => {
  const userRole = store.state.user.role
  // 如果路由需要权限(meta.role存在)
  if (to.meta.role) { 
    if (userRole === to.meta.role) {
      next() // 角色匹配,放行
    } else {
      next('/403') // 角色不匹配,跳权限不足页面
    }
  } else {
    next() // 不需要权限的页面,直接放行
  }
})

这样一来,管理员进不了编辑页面,编辑也进不了管理员页面,权限控制就稳了~

步骤3:结合组件内守卫细化逻辑

全局守卫管页面级权限,组件内守卫可以管“页面内操作”,比如管理员进入仪表盘前,用 beforeRouteEnter 加载数据:

<template>
  <div>{{ dashboardData.title }}</div>
</template>
<script>
export default {
  data() {
    return {
      dashboardData: {}
    }
  },
  beforeRouteEnter(to, from, next) {
    fetchDashboardData().then(res => {
      next(vm => {
        vm.dashboardData = res.data
      })
    })
  }
}
</script>

要是有更细的权限(比如按钮级),可以在组件内根据角色控制渲染,但路由守卫主要负责“页面能不能进”这个大关卡~

用路由守卫容易踩哪些坑?咋避?

路由守卫功能强,但用不对容易出bug,下面列几个常见坑和解决办法~

next() 多次调用

:守卫里 next() 只能调用一次,调多次会报错,比如异步操作没处理好,导致 next() 调了两次:

// 错误示例!next()调了两次
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) {
    checkLogin().then(res => {
      next() // 第一次调
    })
    next() // 第二次调,触发报错!
  }
})

解决:把 next() 放进异步回调里,或者用 if - else 控制,确保只调一次:

// 正确示例:next()只在异步回调里调
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) {
    checkLogin().then(res => {
      next() // 只调一次
    }).catch(err => {
      next('/login')
    })
  } else {
    next()
  }
})

异步操作没等完就跳转

:在 beforeEnterbeforeRouteEnter 里请求数据时,没等请求完成就调用 next(),导致组件拿到空数据。

// 错误示例:没等fetchData完成就next()
beforeEnter: (to, from, next) => {
  fetchData() // 异步请求没等结果
  next() // 直接放行,组件拿到空数据
}

解决:把 next() 放进异步操作的 then 里,确保数据加载完再放行:

// 正确示例:等数据加载完再next()
beforeEnter: (to, from, next) => {
  fetchData().then(res => {
    store.commit('saveData', res)
    next() // 数据存好后再放行
  })
}

守卫执行顺序搞混

:不同守卫触发时机不同,逻辑依赖顺序时容易出错,比如想在组件创建前加载数据,却用了全局 afterEach(此时组件已经创建完了),导致数据加载时机不对。

解决:牢记守卫执行顺序(前面讲过的流程):

全局 beforeEach → 路由独享 beforeEnter → 组件内 beforeRouteEnter → 全局 beforeResolve → 组件实例创建 → 全局 afterEach

逻辑要放在对应时机的守卫里,组件创建前加载数据”→ 用 beforeRouteEnter;“全局统一权限判断”→ 用 beforeEach

组件内 beforeRouteEnter 拿不到 this

beforeRouteEnter 触发时,组件还没实例化,直接用 this 会报错 undefined

解决:要访问组件实例,必须在 next 的回调里操作,vm 就是组件实例:

beforeRouteEnter(to, from, next) {
  next(vm => {
    vm.doSomething() // 正确,vm是组件实例
  })
}

要是直接写 this.doSomething(),肯定报错~

把这些坑避开,路由守卫用起来就顺多啦~

vue - router guard 是路由跳转的“控制中心”,不同类型的守卫各司其职,从全局到单个路由再到组件内,覆盖了跳转的每个环节,只要把类型、用法、权限结合、避坑技巧吃透,就能轻松用它管控页面跳转、做权限管理,让项目路由逻辑更丝滑~要是你还有其他疑问,评论区随时喊我~

版权声明

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

发表评论:

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

热门