跳至内容

导航守卫

顾名思义,Vue Router 提供的导航守卫主要用于保护导航,方法是重定向或取消导航。有多种方法可以挂钩到路由导航流程:全局、每路由或组件内。

全局前置守卫

您可以使用 router.beforeEach 注册全局前置守卫

js
const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // explicitly return false to cancel the navigation
  return false
})

全局前置守卫在创建顺序中调用,每当触发导航时都会调用。守卫可以异步解析,导航在所有钩子解析之前被视为挂起

每个守卫函数接收两个参数

并且可以选择返回以下任何值

  • false: 取消当前导航。如果浏览器 URL 已更改(无论是用户手动更改还是通过后退按钮更改),它将重置为 from 路由的 URL。

  • 一个 路由位置: 通过传递路由位置来重定向到不同的位置,就像您调用 router.push() 一样,这允许您传递选项,例如 replace: truename: 'home'。当前导航被丢弃,并使用相同的 from 创建一个新的导航。

    js
    router.beforeEach(async (to, from) => {
      if (
        // make sure the user is authenticated
        !isAuthenticated &&
        // ❗️ Avoid an infinite redirect
        to.name !== 'Login'
      ) {
        // redirect the user to the login page
        return { name: 'Login' }
      }
    })

如果遇到意外情况,也可以抛出 Error。这也会取消导航并调用通过 router.onError() 注册的任何回调。

如果返回空值、undefinedtrue,则导航被验证,并调用下一个导航守卫。

以上所有内容async 函数和 Promise 的工作方式相同

js
router.beforeEach(async (to, from) => {
  // canUserAccess() returns `true` or `false`
  const canAccess = await canUserAccess(to)
  if (!canAccess) return '/login'
})

可选的第三个参数 next

在早期版本的 Vue Router 中,也可以使用第三个参数 next,这是错误的常见来源,并经过 RFC 删除。但是,它仍然受支持,这意味着您可以将第三个参数传递给任何导航守卫。在这种情况下,您必须在导航守卫的任何给定传递中恰好调用 next 一次。它可以出现多次,但前提是逻辑路径没有重叠,否则钩子将永远不会解析或产生错误。以下是如何错误地将用户重定向到 /login 的示例,如果他们未经身份验证

js
// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // if the user is not authenticated, `next` is called twice
  next()
})

以下是正确的版本

js
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

全局解析守卫

您可以使用 router.beforeResolve 注册全局守卫。这类似于 router.beforeEach,因为它在每次导航时都会触发,但解析守卫在导航确认之前调用,在所有组件内守卫和异步路由组件解析之后。以下是一个示例,它确保用户已授予对具有 已定义自定义元 属性 requiresCamera 的路由的摄像头的访问权限

js
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... handle the error and then cancel the navigation
        return false
      } else {
        // unexpected error, cancel the navigation and pass the error to the global handler
        throw error
      }
    }
  }
})

router.beforeResolve 是获取数据或执行您希望避免在用户无法进入页面时执行的任何其他操作的理想位置。

全局后置钩子

您还可以注册全局后置钩子,但是与守卫不同,这些钩子不会获得 next 函数,也不能影响导航

js
router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

它们对于分析、更改页面标题、辅助功能(例如宣布页面)以及许多其他事情很有用。

它们还反映了 导航失败 作为第三个参数

js
router.afterEach((to, from, failure) => {
  if (!failure) sendToAnalytics(to.fullPath)
})

其指南 上了解有关导航失败的更多信息。

守卫中的全局注入

从 Vue 3.3 开始,可以在导航守卫中使用 inject()。这对于注入全局属性(如 pinia 存储)很有用。使用 app.provide() 提供的任何内容也可以在 router.beforeEach()router.beforeResolve()router.afterEach() 中访问

ts
// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')

// router.ts or main.ts
router.beforeEach((to, from) => {
  const global = inject('global') // 'hello injections'
  // a pinia store
  const userStore = useAuthStore()
  // ...
})

每路由守卫

您可以在路由配置对象的直接定义 beforeEnter 守卫

js
const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

beforeEnter 守卫仅在进入路由时触发,它们不会在 paramsqueryhash 更改时触发,例如从 /users/2/users/3 或从 /users/2#info/users/2#projects。它们仅在从不同路由导航时触发。

您也可以将函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用

js
function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

在使用 嵌套路由 时,父路由和子路由都可以使用 beforeEnter。当放置在父路由上时,它不会在使用相同父路由的子路由之间移动时触发。例如

js
const routes = [
  {
    path: '/user',
    beforeEnter() {
      // ...
    },
    children: [
      { path: 'list', component: UserList },
      { path: 'details', component: UserDetails },
    ],
  },
]

上面的示例中的 beforeEnter 不会在 /user/list/user/details 之间移动时调用,因为它们共享相同的父路由。如果我们将 beforeEnter 守卫直接放在 details 路由上,那么在这些路由之间移动时会调用它。

提示

可以通过使用 路由元字段 和全局导航守卫来实现类似于每路由守卫的行为。

组件内守卫

最后,您可以在路由组件(传递给路由配置的组件)中直接定义路由导航守卫

使用选项 API

您可以将以下选项添加到路由组件

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
vue
<script>
export default {
  beforeRouteEnter(to, from) {
    // called before the route that renders this component is confirmed.
    // does NOT have access to `this` component instance,
    // because it has not been created yet when this guard is called!
  },
  beforeRouteUpdate(to, from) {
    // called when the route that renders this component has changed, but this component is reused in the new route.
    // For example, given a route with params `/users/:id`, when we navigate between `/users/1` and `/users/2`,
    // the same `UserDetails` component instance will be reused, and this hook will be called when that happens.
    // Because the component is mounted while this happens, the navigation guard has access to `this` component instance.
  },
  beforeRouteLeave(to, from) {
    // called when the route that renders this component is about to be navigated away from.
    // As with `beforeRouteUpdate`, it has access to `this` component instance.
  },
}
</script>

beforeRouteEnter 守卫没有访问 this,因为守卫在导航确认之前调用,因此新的进入组件甚至还没有创建。

但是,您可以通过将回调传递给 next 来访问实例。回调将在导航确认时调用,组件实例将作为参数传递给回调

js
beforeRouteEnter (to, from, next) {
  next(vm => {
    // access to component public instance via `vm`
  })
}

请注意,beforeRouteEnter 是唯一支持将回调传递给 next 的守卫。对于 beforeRouteUpdatebeforeRouteLeavethis 已经可用,因此传递回调是不必要的,因此不受支持

js
beforeRouteUpdate (to, from) {
  // just use `this`
  this.name = to.params.name
}

离开守卫通常用于防止用户意外离开具有未保存编辑的路由。可以通过返回 false 来取消导航。

js
beforeRouteLeave (to, from) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (!answer) return false
}

使用组合式 API

如果您使用组合式 API 编写组件,则可以通过 onBeforeRouteUpdateonBeforeRouteLeave 分别添加更新和离开守卫。有关更多详细信息,请参阅 组合式 API 部分

完整的导航解析流程

  1. 触发导航。
  2. 在停用的组件中调用 beforeRouteLeave 守卫。
  3. 调用全局 beforeEach 守卫。
  4. 在重用组件中调用 beforeRouteUpdate 守卫。
  5. 在路由配置中调用 beforeEnter
  6. 解析异步路由组件。
  7. 在激活的组件中调用 beforeRouteEnter
  8. 调用全局 beforeResolve 守卫。
  9. 导航已确认。
  10. 调用全局 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 使用实例化的实例调用传递给 beforeRouteEnter 守卫中 next 的回调。