一,前言
上一篇,介绍了 router-view
组件的实现,主要涉及以下内容:
函数式组件的介绍;
router-view 组件的实现:
获取渲染记录;
标记 router-view 层级深度;
根据深度进行 router-view 渲染;
本篇,介绍 vue-router 全局钩子函数的实现;
二,完整的导航解析流程
路由钩子的渲染流程(完整的导航解析流程):
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
下文以 vue 中最常用的钩子:router.beforeEach 为例进行说明;
三,路由钩子的使用
通过 router.beforeEach 注册 beforeEach 钩子回调函数;
// router.js
router.beforeEach((from,to,next)=>{
console.log(1);
setTimeout(() => {
next();
}, 1000);
})
router.beforeEach((from,to,next)=>{
console.log(2);
setTimeout(() => {
next();
}, 1000);
})
复制代码
同一个钩子可以多次进行注册,当触发执行时,会按照注册顺序依次执行对应函数;
使用这种写法,就可以按照不同功能进行逻辑隔离;比如:一个做权限控制,一个做动态路由;
观察钩子函数的使用方式:先多次注册,后依次调用;这就是类似于发布订阅模式;
四,路由钩子的实现
1,创建 router.beforeEach 方法 - 钩子函数的订阅
根据发布订阅模式:
首先,需要在 router 实例上增加一个 beforeEach 方法;
VueRouter 实例化时,创建 beforeHooks 数组,用于存放注册的钩子函数;
当执行 router.beforeEach 时,将钩子函数 push 到 beforeHooks 数组中,相当于订阅;
// index.js
class VueRouter {
constructor(options) { // 传入配置对象
// 定义一个存放钩子函数的数组
this.beforeHooks = [];
}}
// 在router.beforeEach时,依次执行注册的钩子函数
beforeEach(fn){
this.beforeHooks.push(fn);
}
}
export default VueRouter;
复制代码
2,beforeEach 钩子的执行时机
当路径切换时,需要让 beforeHooks 数组中注册的函数依次执行;
beforeEach 钩子的执行时机:路由已经开始切换,但还没有更新之前:
所以,beforeEach 钩子函数的代码执行位置:在 transitionTo 切换路由方法中,且在执行 updateRoute 方法之前;
// history/base.js
class History {
constructor(router) {
this.router = router;
}
/**
* 路由跳转方法:
* 每次跳转时都需要知道 from 和 to
* 响应式数据:当路径变化时,视图刷新
* @param {*}} location
* @param {*} onComplete
*/
transitionTo(location, onComplete) {
let route = this.router.match(location);
if (location == this.current.path && route.matched.length == this.current.matched.length) {
return
}
// beforeEach的执行时机:
// 在 transitionTo 切换路由方法中,且在执行 updateRoute 方法之前;
this.updateRoute(route);
onComplete && onComplete();
}
}
export { History }
复制代码
在更新之前调用注册好的导航守卫,执行完成后,执行 updateRoute 和 onComplete() 这两步逻辑;
3,执行注册的钩子函数
在 base.js 中,通过 this.router.beforeHooks 拿到 hook 数组(base.js 中的 History 类中有 router 实例,而 beforeHooks 数组是声明在 router 实例上的)
由于 beforeHooks 可能存在多个函数,需要全部执行完成后,才可以继续执行后面的 updateRoute 和 onComplete() 这两步;
所以,需要一个执行全部回调函数的队列 runQueue:每次执行时,调用 iterator 迭代器方法,一个一个队列进行迭代,全部完成之后,继续执行 updateRoute 和 onComplete();
runQueue 的作用:将注册进来的钩子函数依次执行,并调用传入的 iterator;
runQueue 的核心思想是异步迭代;
// history/base.js
/**
* 递归执行钩子函数
* @param {*} queue 钩子函数队列
* @param {*} iterator 执行钩子函数的迭代器
* @param {*} cb 全部执行完成后调用
*/
function runQueue(queue, iterator, cb) {
// 异步迭代
function step(index) {
// 结束条件:队列全部执行完成,执行回调函数 cb 更新路由
if (index >= queue.length) return cb();
let hook = queue[index]; // 先执行第一个 将第二个hook执行的逻辑当做参数传入
iterator(hook, () => step(index + 1));
}
step(0);
}
复制代码
将所有钩子函数拼接到一起,并通过 runQueue 队列执行所有函数:
// history/base.js
class History {
constructor(router) {
this.router = router;
}
/**
* 路由跳转方法:
* 每次跳转时都需要知道 from 和 to
* 响应式数据:当路径变化时,视图刷新
* @param {*}} location
* @param {*} onComplete
*/
transitionTo(location, onComplete) {
let route = this.router.match(location);
if (location == this.current.path && route.matched.length == this.current.matched.length) {
return
}
// 获取到注册的回调方法
let queue = [].concat(this.router.beforeHooks);
const iterator = (hook, next) => {
hook(this.current, route, () => {
next();
})
}
runQueue(queue, iterator, () => {
// 将最后的两步骤放到回调中,确保执行顺序
// 1,使用当前路由route更新current,并执行其他回调
this.updateRoute(route);
// 根据路径加载不同的组件 this.router.matcher.match(location) 组件
// 2,渲染组件
onComplete && onComplete();
})
}
}
export { History }
复制代码
五,结尾
本篇,介绍了全局钩子函数的实现,主要涉及以下内容:
导航解析流程;
路由钩子函数的使用和原理;
路由钩子函数的实现;
下篇,待定;
评论