导航守卫
“导航”表示路由正在发生改变。
“守卫”其实含义就是函数钩子。
导航守卫就是一个导航发生时候的钩子函数。导航守卫主要用来通过 跳转 或 取消 的方式守卫导航。params或 query 的改变并不会触发导航守卫。
导航的守卫可以是全局的,单个路由独享的,或者组件级的。
全局前置守卫
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})守卫的回调函数被认为是 异步 的,此时导航在所有守卫 resolve 完之前,一直处于 pending:
callback = (to, from) => {};
res = null;
to = {};
from = {};
res = await Promise.resolve(callback(to, from));对于 callback.length === 3 的情况,即传入了 next 参数(它在新版本的 vue-router 中已经废弃,所以以后也不应该使用这个历史上被设计的值),应保证回调内部在执行过程中总是调用并且只调用一次 next:
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。当 res 为 false 的时候, 表示中断当前的导航;URL 地址会强制重置到 from 路由对应的地址。
全局后置守卫
它也是全局的,是整个路由过程已经完全结束了(无论结果是否跳转了),才被调用。它是本次路由的结束钩子。
router.afterEach((to, from, isFail) => {});全局解析守卫
router.beforeResolve((to, from) => {})它也是全局的,在所有组件内守卫和路由组件被解析之后被调用。
此时 url 已经跳转,组件解析完毕,但是还没有开始加载。
如果 beforeEach 得到的 res 是一个 false,那么显然路由不会跳转,也不会有 router-view 被重新渲染,自然也不会有组件被解析,该钩子将不会被调用。
局部守卫
可以单独为某个路由定义钩子 beforeEnter :
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]beforeEnter 可以是一个函数,也可以是一个函数数组,数组内的所有函数都将被当作钩子执行。(这通常用于复用某些函数)
组件守卫
最后,可以在组件内直接定义路由导航守卫(传递给路由配置的):
- beforeRouteLeave
- beforeRouteUpdate
- beforeRouteEnter
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指的是全局的路由配置,比如路由跳转的配置,各种钩子。