四十三、导航守卫


所谓导航守卫,就是在当你进行导航的时候将其拦截下来,从而方便做一些其他的事情。

快速上手

// 全局导航守卫
router.beforeEach((to, from, next) => {
  // 回调函数里面决定了拦截下来后做什么
  console.log('from:', from)
  console.log('to:', to)
  console.log('导航到:', to.name)
  next() // 调用该方法代表放行
})

这是一个全局导航守卫,回调会自动传入 3 个参数:

  • to:即将要进入的目标路由,是一个对象,对象里面有 path、fullPath、hash、params 等参数
  • from:当前导航正要离开的路由,同样是一个对象,对象内部有上述参数
  • next:是一个函数,表示导航放行

各种拦截守卫

整体分为 3 大类:

  1. 全局守卫
    • beforeEach:全局前置守卫,会在解析组件守卫和异步路由组件之前被调用
    • beforeResolve:全局解析守卫,在导航被确认之前,但在所有组件内守卫和异步路由组件被解析之后调用
      • 上面两个其实就是执行的时机一前一后
    • afterEach:全局后置守卫,在导航确认后触发的钩子函数。该钩子函数执行后会触发 DOM 更新,用户看到新的页面。
      • 思考🤔:既然导航都已经确认了,这里安插一个守卫干嘛呢?
      • 全局后置守卫经常用于如下的场景:
        1. 记录页面访问历史:可以使用 afterEach 来记录用户访问的页面,以便进行统计或分析。
        2. 关闭加载指示器:在 beforeEach 中开启加载指示器,在 afterEach 中关闭它,以提供更好的用户体验。
        3. 页面切换动画:在 afterEach 中触发页面切换动画或其他视觉效果,以提升用户体验。
        4. 更新文档标题:在导航完成后更新页面标题,以反映当前页面内容。
  2. 路由守卫 beforeEnter:针对特定路由设置的守卫,因此设置的方式也不再是在 router 路由实例上面设置,而是在某个路由配置中设置。
const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    // 在路由的配置中进行设置,只针对特定的路由进行拦截
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

相关细节:

  1. beforeEnter 守卫只在进入路由时触发不会在 params、query 或 hash 改变时触发
  • 从 /users/2 进入到 /users/3 这种不会触发
  • 从 /users/2#info 进入到 /users/2#projects 这种也不会触发
  1. 如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发。
const routes = [
  {
    path: '/user',
    beforeEnter() {
      // ...
    },
    children: [
      { path: 'list', component: UserList },
      { path: 'details', component: UserDetails },
    ],
  },
]

从 /user/list 跳转到 /user/details 不会触发路由级别守卫。

  1. 组件守卫:这种守卫是组件级别,取决于是否进入或者离开某个组件
    • beforeRouteEnter:进入了该导航,组件开始渲染之前
    • beforeRouteUpdate:当前路由改变,但是该组件被复用时调用,例如对于一个带有动态参数的路径 /users/:id,在 /users/1 和 /users/2 之间跳转的时候会触发
    • beforeRouteLeave:离开了该导航,组件失活的时候

整体的执行顺序:

  1. 组件离开守卫
  2. 全局前置守卫
  3. 路由级别守卫
  4. 全局解析守卫
  5. 全局后置守卫
  6. 组件进入守卫

如果是组件复用,参数变化的情况,执行顺序如下:

  1. 全局前置守卫
  2. 组件更新守卫
  3. 全局解析守卫
  4. 全局后置守卫

其他细节

1. 路由级别守卫beforeEnter设置多个值

路由级别守卫,也就是 beforeEnter 可以设置成一个数组,数组里面包含多个方法,每个方法进行一项处理。

const routes = [
  // ...
  {
    path: '/about',
    name: 'About',
    component: About,
    beforeEnter: [
      (to, from, next) => {
        console.log('Route beforeEnter step 1')
        next()
      },
      (to, from, next) => {
        console.log('Route beforeEnter step 2')
        next()
      }
    ]
  },
  // ...
]

2. 在守卫内的全局注入

Vue 3.3 开始,你可以在导航守卫内使用 inject() 方法。这在注入像 pinia stores 这样的全局属性时很有用。

在 app.provide() 中提供的所有内容都可以在全局守卫里面获取到。

// main.js
const app = createApp();
app.provide('global', 'some data');
// router.js
router.beforeEach(()=>{
  const data = inject('global');

  const userStore = useUserStore();
})

实战案例

使用导航守卫拦截未登录的用户,将未登录用户导航到登录页面。

角色:普通用户、管理员

页面:主页、用户页、管理员页、登录

未登录:主页、登录

用户身份登录:主页、用户页、登录

管理员身份登录:主页、用户页、管理员页、登录


文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录