Vue路由守卫:给你的页面访问加把锁!

332 阅读3分钟

大家好,我是小杨,一个在Vue项目中被路由守卫"救过命"的前端老司机。今天咱们来聊聊Vue项目中那个既强大又容易踩坑的功能——路由守卫。这玩意儿就像是你网站的保安系统,用好了能挡掉各种非法访问,用不好可能连自己都进不去!

1. 路由守卫是啥?先看个真实案例

去年我做了一个后台管理系统,上线后客户说:"怎么谁都能进管理员页面?" 当时我就懵了,赶紧加上了路由守卫:

router.beforeEach((to, from, next) => {
  if (to.path.startsWith('/admin') && !store.state.isAdmin) {
    next('/login') // 不是管理员?门口等着!
  } else {
    next() // 自己人,请进~
  }
})

就这么几行代码,解决了大问题!这就是路由守卫的威力。

2. 三种守卫类型:门卫、房间管家和清洁工

Vue路由提供了三种守卫,各司其职:

2.1 全局守卫(大门保安)

  • beforeEach:进门前检查
  • afterEach:进门后记录
// 记录页面访问
router.afterEach((to, from) => {
  analytics.trackPageView(to.path) // 打个卡
})

2.2 路由独享守卫(房间管家)

只在特定路由生效:

{
  path: '/dashboard',
  component: Dashboard,
  beforeEnter: (to, from, next) => {
    if (!store.state.user) {
      next('/login') // 没带门卡不让进
    } else {
      next()
    }
  }
}

2.3 组件内守卫(房间内的规则)

直接在组件里定义:

export default {
  beforeRouteEnter(to, from, next) {
    // 还不能访问this,因为组件实例还没创建
    next(vm => {
      // 现在可以通过vm访问组件实例
    })
  },
  
  beforeRouteUpdate(to, from, next) {
    // 当前路由改变,但该组件被复用时调用
    this.fetchData(to.params.id) // 比如重新获取数据
    next()
  },
  
  beforeRouteLeave(to, from, next) {
    // 离开前检查
    if (this.hasUnsavedChanges) {
      if (confirm('有未保存的更改,确定离开?')) {
        next()
      } else {
        next(false) // 取消导航
      }
    } else {
      next()
    }
  }
}

3. 我的血泪教训:守卫中的异步操作

有一次我在beforeRouteEnter里写了个异步请求:

// ❌ 错误示范
beforeRouteEnter(to, from, next) {
  api.checkPermission().then(res => {
    if (res.valid) {
      next()
    } else {
      next('/denied')
    }
  })
}

结果页面卡住不动了!后来才明白,beforeRouteEnter的next函数不会等待Promise。正确做法:

// ✅ 正确做法
beforeRouteEnter(to, from, next) {
  next(vm => {
    vm.checkPermission() // 在组件实例创建后调用
  })
}

4. 权限控制实战:RBAC方案

在最近的项目中,我实现了一套基于角色的权限控制:

// 权限检查函数
function checkPermission(to, user) {
  const requiredRole = to.meta.role
  if (!requiredRole) return true
  return user.roles.includes(requiredRole)
}

// 全局守卫
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !store.state.user) {
    return next('/login')
  }
  
  if (!checkPermission(to, store.state.user)) {
    return next('/403') // 无权限页面
  }
  
  next()
})

对应的路由配置:

{
  path: '/admin',
  meta: {
    requiresAuth: true,
    role: 'ADMIN'
  }
}

5. 常见问题解答

Q:为什么我的守卫执行了两次?
A:可能是路由重复注册或者守卫里有重定向循环

Q:如何取消导航?
A:调用next(false)即可

Q:路由变化但组件没更新?
A:试试在beforeRouteUpdate里处理数据更新

6. 性能优化小技巧

守卫里不要放重逻辑!我曾经见过这样的代码:

// ❌ 性能杀手
beforeEach(async (to, from, next) => {
  const user = await fetchUser() // 每次路由变化都请求用户信息
  // ...
})

应该改成:

// ✅ 优化方案
let userPromise = null

router.beforeEach(async (to, from, next) => {
  if (!userPromise) {
    userPromise = fetchUser()
  }
  const user = await userPromise
  // ...
})

总结

路由守卫是Vue项目中的瑞士军刀,能处理:

  • 权限控制
  • 数据预取
  • 页面访问记录
  • 表单未保存提示
  • 等等...

记住几个原则:

  1. 全局守卫处理通用逻辑
  2. 路由独享守卫处理特定规则
  3. 组件内守卫处理组件相关逻辑