Skip to content

导航守卫

“导航”表示路由正在发生改变。

“守卫”其实含义就是函数钩子。

导航守卫就是一个导航发生时候的钩子函数。导航守卫主要用来通过 跳转 或 取消 的方式守卫导航。paramsquery 的改变并不会触发导航守卫。

导航的守卫可以是全局的,单个路由独享的,或者组件级的。

全局前置守卫

js
const router = new VueRouter({ ... })

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

守卫的回调函数被认为是 异步 的,此时导航在所有守卫 resolve 完之前,一直处于 pending:

js
callback = (to, from) => {};

res = null;
to = {};
from = {};

res = await Promise.resolve(callback(to, from));

对于 callback.length === 3 的情况,即传入了 next 参数(它在新版本的 vue-router 中已经废弃,所以以后也不应该使用这个历史上被设计的值),应保证回调内部在执行过程中总是调用并且只调用一次 next

js
callback = (to, from, next) => {};

res = null;
to = {};
from = {};
next(res) {
    this.res = res;
}

if(callback.length === 3) {
    callback(to, from, next);
};

上述两种情况(包含 next 与否),res 的结果必须为 boolean 或者 string。当 resfalse 的时候, 表示中断当前的导航;URL 地址会强制重置到 from 路由对应的地址。

全局后置守卫

它也是全局的,是整个路由过程已经完全结束了(无论结果是否跳转了),才被调用。它是本次路由的结束钩子。

js
router.afterEach((to, from, isFail) => {});

全局解析守卫

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

它也是全局的,在所有组件内守卫路由组件被解析之后被调用。

此时 url 已经跳转,组件解析完毕,但是还没有开始加载。

如果 beforeEach 得到的 res 是一个 false,那么显然路由不会跳转,也不会有 router-view 被重新渲染,自然也不会有组件被解析,该钩子将不会被调用。

局部守卫

可以单独为某个路由定义钩子 beforeEnter

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

beforeEnter 可以是一个函数,也可以是一个函数数组,数组内的所有函数都将被当作钩子执行。(这通常用于复用某些函数)

组件守卫

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

  • beforeRouteLeave
  • beforeRouteUpdate
  • beforeRouteEnter
js
const Foo = {
  template: `...`,\
  beforeRouteLeave(to, from, next) {
  // 导航离开该组件的对应路由时调用
  // 可以访问组件实例 `this`
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。可以用它更新组件中的数据。
    // 可以访问组件实例 `this`。
  },
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
}

钩子触发的顺序全景图

首先需要知道的是,当点击 router-link 后,如果 url 完全没有变,那么这个情况下只会触发唯一的路由结束钩子 afterEach。下面讨论的是 url 变化的情况。

当路由发生变化的时候(或者新建标签页打开新的 url,也算变化),我们将钩子触发的顺序分为两种情况,第一种是组件会被替换,第二中则是组件没有被替换。

组件更新的情况

新路由处理阶段

  • 导航被触发。
  • 在旧组件里调用 beforeRouteLeave
  • 调用全局的 beforeEach
  • 匹配新路由,找到新的目标组件。
  • 调用新路由的 beforeEnter

新组件解析阶段

  • 开始解析新组件(如果需要)
  • 调用组件中的 beforeRouteEnter
  • 组件全部解析完毕。调用全局的 beforeResolve

收尾

  • 调用全局的 afterEach,导航结束。
  • 触发 DOM 挂载、更新。这将触发 beforeRouteEnter 中传给 next 的回调(如果有的话),这个回调的触发时机有点类似于 onMounted。他会将创建好的组件 DOM 实例会作为参数传入。这个钩子完全可以用 onMounted 来代替。

组件没有更新的情况

新路由处理阶段

  • 导航被触发。
  • 调用全局的 beforeEach
  • 调用组件内beforeRouteUpdate(如果有的话)。

收尾

  • 组件不需重新卸载解析。直接调用全局的 beforeResolve
  • 调用全局的 afterEach,导航结束。

总结

可以看到,总结出的执行顺序大概是:

旧组件(如果有) -> 全局 -> 新路由 -> (新)组件 -> 全局`

另外,在 vue setup 语法的 router 版本中,组件内的路由生命钩子从三个变为了两个,考虑到 setup 本身就和这个生命周期完全等价,最复杂的那个 beforeRouteEnter 没有了(说复杂,是因为它是唯一一个 next 参数可以传递第二个回调的 vue-router 钩子)。由于没有了这个钩子,可以发现现在整个路由钩子的执行顺序,和总结出的顺序完全吻合了。

另外显然,对于一个成功跳转的路由的完整过程,上面的所有生命周期钩子,它们的 res 都应该返回非 false 值。

route 和 router

  • route 指的是单个 url 的信息,比如 name, params, hash, query, fullPath 等等。
  • router 指的是全局的路由配置,比如路由跳转的配置,各种钩子。