写点什么

【VueRouter 源码学习】第十篇 - 全局钩子函数的实现

用户头像
Brave
关注
发布于: 7 小时前
【VueRouter 源码学习】第十篇 -  全局钩子函数的实现

一,前言


上一篇,介绍了 router-view 组件的实现,主要涉及以下内容:


  • 函数式组件的介绍;

  • router-view 组件的实现:

  • 获取渲染记录;

  • 标记 router-view 层级深度;

  • 根据深度进行 router-view 渲染;


本篇,介绍 vue-router 全局钩子函数的实现;


二,完整的导航解析流程


路由钩子的渲染流程(完整的导航解析流程):

  1. 导航被触发。

  2. 在失活的组件里调用 beforeRouteLeave 守卫。

  3. 调用全局的 beforeEach 守卫。

  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。

  5. 在路由配置里调用 beforeEnter。

  6. 解析异步路由组件。

  7. 在被激活的组件里调用 beforeRouteEnter。

  8. 调用全局的 beforeResolve 守卫 (2.5+)。

  9. 导航被确认。

  10. 调用全局的 afterEach 钩子。

  11. 触发 DOM 更新。

  12. 调用 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 钩子的执行时机:路由已经开始切换,但还没有更新之前:


  • 在哪里做切换?base.js 中的 transitionTo 方法中进行切换;

  • 在哪里做更新?updateRoute 方法中进行赋值更新;


所以,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 }
复制代码



五,结尾


本篇,介绍了全局钩子函数的实现,主要涉及以下内容:


  • 导航解析流程;

  • 路由钩子函数的使用和原理;

  • 路由钩子函数的实现;


下篇,待定;

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【VueRouter 源码学习】第十篇 -  全局钩子函数的实现