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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一前言

上一篇介绍了 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;

2beforeEach 钩子的执行时机

当路径切换时需要让 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 }

五结尾

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

  • 导航解析流程
  • 路由钩子函数的使用和原理
  • 路由钩子函数的实现

下篇待定

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: vue