如何利用`Vue Router`的`beforeEach`与`afterEach`进行全局导航守卫?

Vue Router 全局导航守卫:beforeEachafterEach 的深度剖析

大家好!今天我们来深入探讨 Vue Router 中两个非常重要的全局导航守卫:beforeEachafterEach。它们是构建复杂、安全且用户体验良好的单页面应用 (SPA) 的关键工具。我们将通过讲解、代码示例和场景分析,充分理解它们的功能和应用。

导航守卫概述

Vue Router 的导航守卫主要用于在路由导航发生时,对导航过程进行控制和干预。可以进行权限验证、页面统计、数据预取等操作。 导航守卫本质上是函数,这些函数会在路由导航的不同阶段被调用。

主要有三种类型的导航守卫:

  1. 全局守卫: 作用于整个应用,每次路由切换都会触发。
  2. 路由独享守卫: 只对特定路由生效。
  3. 组件内的守卫: 定义在组件内部,只对该组件的路由生效。

今天我们主要讨论的是全局守卫 beforeEachafterEach

beforeEach 守卫:导航前的拦截器

beforeEach 守卫会在每次路由导航 开始前 被调用。它允许你拦截或取消导航,以及执行一些必要的操作。它的函数签名如下:

router.beforeEach((to, from, next) => {
  // ...
})
  • to: Route:即将要进入的目标路由对象。 包含了 path, params, query, meta 等信息。
  • from: Route:当前导航正要离开的路由对象。同样包含 path, params, query, meta 等信息。
  • next: Function:一个函数,用于控制导航的流程。 必须调用 next() 才能resolve这个hook。

next 函数有以下几种用法:

  • next(): 进行管道中的下一个钩子。如果所有钩子都执行完了,则导航的状态就是 confirmed (有效)。
  • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • next(path: string)next({ path: string, ... }): 重定向到不同的地址。参数和 router.push 方法的参数一样。
  • next(error: Error): 如果传入 Error 的实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

示例:权限验证

一个常见的应用场景是权限验证。假设我们有一个需要用户登录才能访问的后台管理页面。我们可以使用 beforeEach 守卫来检查用户是否已经登录,如果没有登录,则重定向到登录页面。

import router from './router' // 假设 router 实例在这里定义

router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('token') // 假设 token 存在 localStorage 中

  if (to.meta.requiresAuth && !isAuthenticated) {
    // 如果目标路由需要身份验证,并且用户未登录
    next('/login') // 重定向到登录页面
  } else {
    next() // 允许导航
  }
})

// 路由配置示例:
const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    meta: { requiresAuth: true } // 标记该路由需要身份验证
  },
  {
    path: '/login',
    component: Login
  }
]

在这个例子中,我们首先从 localStorage 中获取 token,判断用户是否登录。然后,我们检查目标路由的 meta 字段中是否包含 requiresAuth: true。如果两者都满足,则说明该路由需要身份验证,并且用户未登录,因此我们将用户重定向到登录页面。

示例:页面统计

另一个常见的应用场景是页面统计。我们可以在 beforeEach 守卫中发送页面访问统计数据。

import router from './router'

router.beforeEach((to, from, next) => {
  // 发送页面统计数据
  sendPageView(to.path) // 假设 sendPageView 是一个发送统计数据的函数
  next()
})

示例:动态设置页面标题

可以根据路由的 meta 字段动态设置页面标题。

router.beforeEach((to, from, next) => {
  document.title = to.meta.title || 'Default Title';
  next();
});

// 路由配置示例:
const routes = [
  {
    path: '/about',
    component: About,
    meta: { title: 'About Us' }
  },
  {
    path: '/contact',
    component: Contact,
    meta: { title: 'Contact Us' }
  }
];

注意事项:

  • 必须调用 next 函数: 如果在 beforeEach 守卫中没有调用 next 函数,导航将会被阻塞,页面将无法跳转。这是一个常见的错误。
  • 避免死循环: 如果重定向的路由也需要身份验证,可能会导致死循环。 需要谨慎设计路由和守卫逻辑,确保不会出现无限重定向的情况。例如,在上面的权限验证例子中,应该确保登录页面不需要身份验证。
  • 异步操作: beforeEach 守卫支持异步操作。 例如,可以异步获取用户信息,然后根据用户信息决定是否允许导航。 如果使用了异步操作,需要在 next 函数中传递 resolvereject

afterEach 守卫:导航完成后的钩子

afterEach 守卫会在每次路由导航 完成后 被调用。 与 beforeEach 不同,afterEach 守卫 不能 阻止导航的发生。 它主要用于执行一些在导航完成后需要执行的操作,例如页面统计、清理工作等。 它的函数签名如下:

router.afterEach((to, from) => {
  // ...
})
  • to: Route:即将要进入的目标路由对象。
  • from: Route:当前导航正要离开的路由对象。

示例:页面滚动到顶部

一个常见的应用场景是在导航完成后,将页面滚动到顶部,提升用户体验。

import router from './router'

router.afterEach((to, from) => {
  window.scrollTo(0, 0) // 滚动到页面顶部
})

示例:发送页面统计数据 (异步)

beforeEach 中的页面统计不同,afterEach 可以在导航完成后发送统计数据,确保统计数据的准确性。 并且可以将一些耗时的统计任务放在afterEach中执行,避免影响导航速度。

import router from './router'

router.afterEach((to, from) => {
  // 异步发送页面统计数据
  setTimeout(() => {
    sendPageView(to.path) // 假设 sendPageView 是一个发送统计数据的函数
  }, 0)
})

注意事项:

  • 不能阻止导航: afterEach 守卫不能阻止导航的发生。 即使在 afterEach 守卫中抛出错误,导航仍然会完成。
  • 异步操作: afterEach 守卫支持异步操作。 但是,由于它不能阻止导航,因此异步操作的结果不会影响导航流程。
  • 错误处理: 如果在 afterEach 守卫中发生错误,可以使用 router.onError 钩子来捕获错误。

beforeResolve 守卫

除了 beforeEachafterEach 之外,还有一个全局守卫 beforeResolve。它和 beforeEach 类似,会在所有组件内的 beforeRouteEnter 守卫和异步路由组件被解析之后,导航被确认之前调用。

router.beforeResolve((to, from, next) => {
  // ...
})

beforeResolve 通常用于在导航被确认之前执行一些最后的操作,例如获取一些必须的数据。

导航守卫的执行顺序

当一个导航触发时,导航守卫的执行顺序如下:

  1. 离开守卫 (组件内的 beforeRouteLeave 守卫)
  2. 全局 beforeEach 守卫
  3. 重用守卫 (组件内的 beforeRouteUpdate 守卫)
  4. 路由独享的 beforeEnter 守卫
  5. 解析异步路由组件
  6. 进入守卫 (组件内的 beforeRouteEnter 守卫)
  7. 全局 beforeResolve 守卫
  8. 导航被确认
  9. 全局 afterEach 守卫
  10. 触发 DOM 更新
  11. 调用组件内的 mounted 钩子 (如果组件被渲染)

可以用表格来总结:

阶段 守卫类型 说明
导航开始前 beforeRouteLeave 组件内的离开守卫,允许组件在离开路由前执行一些操作,例如保存数据、取消订阅等。
导航开始前 beforeEach 全局前置守卫,每次路由跳转都会触发,可以进行权限验证、页面统计等操作。
导航开始前 beforeRouteUpdate 组件内的重用守卫,当路由参数发生变化但组件被复用时触发,可以更新组件的数据。
导航开始前 beforeEnter 路由独享守卫,只在进入特定路由时触发,可以进行一些特定的验证或数据获取操作。
异步组件解析 N/A 如果路由使用了异步组件,则需要先解析异步组件。
导航确认前 beforeRouteEnter 组件内的进入守卫,只在进入路由时触发,由于此时组件尚未创建,不能访问 this,可以通过 next 回调函数来访问组件实例。
导航确认前 beforeResolve 全局解析守卫,在所有组件内的 beforeRouteEnter 守卫和异步路由组件被解析之后,导航被确认之前调用,用于执行一些最后的操作。
导航完成后 afterEach 全局后置守卫,每次路由跳转完成后触发,可以进行页面统计、清理工作等操作,但不能阻止导航的发生。
组件挂载 mounted 组件挂载到 DOM 后触发,可以进行一些 DOM 操作或初始化工作。

实际应用场景分析

  1. 用户认证与授权:

    • beforeEach: 验证用户是否已登录,如果未登录则重定向到登录页面。 还可以根据用户的角色权限,判断用户是否有权访问目标路由。
    • afterEach: 记录用户访问的页面,用于用户行为分析。
  2. 数据预取:

    • beforeResolve: 在导航被确认之前,预取页面所需的数据,提高页面加载速度。
  3. 滚动行为管理:

    • afterEach: 将页面滚动到顶部,或者恢复到上次滚动的位置。
  4. 多语言支持:

    • beforeEach: 根据用户的语言设置,切换页面的语言。
  5. SEO 优化:

    • afterEach: 更新页面的 meta 信息,例如 description、keywords 等,提高搜索引擎的排名。

高级技巧

  1. 使用 meta 字段传递数据: 可以在路由配置中使用 meta 字段来传递数据,例如权限信息、页面标题等。 然后在导航守卫中读取 meta 字段,进行相应的处理。
  2. 使用自定义的 next 函数: 可以自定义 next 函数,来实现更复杂的导航控制逻辑。 例如,可以根据用户的角色权限,动态生成菜单。
  3. 使用 Promise 封装异步操作: 可以使用 Promise 封装异步操作,使代码更加清晰易懂。
  4. 使用 try...catch 捕获错误: 可以使用 try...catch 捕获导航守卫中的错误,避免影响导航流程。

代码示例:完整的权限控制流程

// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../components/Home.vue'
import Login from '../components/Login.vue'
import Admin from '../components/Admin.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/', component: Home },
  { path: '/login', component: Login },
  { path: '/admin', component: Admin, meta: { requiresAuth: true, requiresAdmin: true } }
]

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('token')
  const userRole = localStorage.getItem('role') // 假设 role 存在 localStorage 中

  if (to.meta.requiresAuth) {
    if (!isAuthenticated) {
      next('/login')
    } else {
      if (to.meta.requiresAdmin && userRole !== 'admin') {
        next('/') // 重定向到首页或其他无权限页面
      } else {
        next()
      }
    }
  } else {
    next()
  }
})

export default router
// Login.vue
<template>
  <div>
    <h1>Login</h1>
    <button @click="login">Login</button>
  </div>
</template>

<script>
export default {
  methods: {
    login() {
      // 模拟登录
      localStorage.setItem('token', 'dummy_token')
      localStorage.setItem('role', 'admin') // 或者 'user'
      this.$router.push('/admin')
    }
  }
}
</script>

在这个例子中,我们不仅验证了用户是否已登录,还验证了用户是否具有管理员权限。只有登录用户且具有管理员权限才能访问 /admin 页面。

总结

beforeEachafterEach 是 Vue Router 中非常强大的全局导航守卫。 beforeEach 允许你在导航开始前进行拦截和控制,而 afterEach 允许你在导航完成后执行一些操作。 合理使用它们,可以构建出安全、稳定且用户体验良好的单页面应用。 掌握这些导航守卫,可以更灵活地处理路由控制、权限验证、页面统计等常见需求,提升应用的整体质量。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注