调用 init 时机

  • 全局混入会影响后续创建的所有 Vue 实例,所以 beforeCreate 首次触发是在 Vue 根实例实例化的时候即 new Vue({router})时,触发后调用 router 实例的 init 方法并传入 Vue 根实例,完成初始化流程;
  • 由于 router 仅存在于 Vue 根实例的 $options 上,所以整个初始化只会被调用一次;
// 1.用来避免将 Vue 做为依赖打包进来
export let Vue; 

import RouterLink from './components/router-link';
import RouterView from './components/router-view';

const install = function (_Vue) {
  if (install.installed && _Vue === Vue) return // 避免重复安装
  install.installed = true

  Vue = _Vue; // 将 Vue 构造函数存储起来

  // 2.注册 router-view、router-link 全局组件
  Vue.component('router-link', RouterLink);
  Vue.component('router-view', RouterView);

  // 3.全局混入将 router实例注册到了 new Vue,这样每个子组件都可以获取到 router 属性
  Vue.mixin({
    beforeCreate() { // mixin(当前options) 会调用 mergeOptions(全局Options,当前options) 进行合并
      // 如果有 router 说明在根实例上增加了router实例,当前这个this实例是根实例
      // 渲染流程先父后子,渲染完毕先子后父 
      if (this.$options.router) {
        this._router = this.$options.router; // 将 router实例 挂载到根实例的 _router 上
        this._routerRoot = this; // 将vm根实例放到了vm根实例的 _routerRoot 上
        this._router.init(this); // 调用当前 router 实例中的 init 方法
        // 若用户更改了 current 是没效果的,需要把 _route 也进行更新(history里面进行了更新)
        // 将 _route 转成响应式的,_route 变化去更新视图
        Vue.util.defineReactive(this, '_route', this._router.history.current);
      } else {
        // 孩子组件获取 _routerRoot,由于渲染流程先父后子,父亲一定先有 _routerRoot 属性
        this._routerRoot = this.$parent && this.$parent._routerRoot;
      }
      // 这样所有的组件都拥有了 this._routerRoot、this._routerRoot._router 属性
    }
  });

  // 4.添加实例属性:将 $route、$router 代理到 vm 实例上
  Object.defineProperty(Vue.prototype, '$route', { // 存放的都是属性 path,matched
    get() {
      return this._routerRoot && this._routerRoot._route; // 取 current
    }
  })
  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      return this._routerRoot && this._routerRoot._router;
    }
  })
}
export default install;

init 方法

import install from './install';
import createMatcher from './create-matcher';
import HashHistory from './history/hashHistory';
import BrowserHistory from './history/browserHistory';

class VueRouter {
  constructor(options) {
    // 1.接收 RouterOptions
    // 2.创建匹配器的过程,并返回 match, addRoutes 方法
    // match函数:匹配功能
    // addRoutes函数:可以添加匹配,动态路由添加
    this.matcher = createMatcher(options.routes || []);
    // 3.创建路由模式,默认值是 hash 模式
    this.mode = options.mode || 'hash';
    switch (this.mode) {
      case 'hash':
        this.history = new HashHistory(this)
        break;
      case 'history':
        this.history = new BrowserHistory(this);
        break;
    }
    // 4.初始化钩子队列(这里只初始化 beforeHooks)
    this.beforeHooks = [];
  }
  match(location) {
      return this.matcher.match(location);
  }
  init(app) { // 目前这个 app 指代的就是最外层 new Vue 根实例
    // 需要根据用户配置,做出一个映射表
    // 需要根据当前路径,实现页面跳转的逻辑
    const history = this.history;

    // 进行匹配操作,根据路径获取对应的记录
    let setupHashListener = () => {
      history.setupListener(); // 设置 hashchange 监听事件
    }

    // 根据 history(hash、browser)类型,调用 transitionTo 跳转到不同的初始页面
    history.transitionTo(history.getCurrentLocation(), setupHashListener)

    // 注册 updateRoute 回调,在 router 更新时,更新 app._route 完成页面重新渲染
    history.listen((route) => { app._route = route })
  }
  push(location) {
    const history = this.history;
    window.location.hash = location;
  }
  beforeEach(fn) {
    this.beforeHooks.push(fn);
  }
}

VueRouter.install = install;
export default VueRouter;

transitionTo

// 根据匹配到的记录来计算匹配到的所有的记录  
export const createRoute = (record, location) => {
  let matched = []
  if (record) {
    while (record) {
      matched.unshift(record);
      record = record.parent; // 通过当前记录找到所有的父亲 
    }
  }
  // {path:'/',matched:[{}]}
  return { ...location, matched };
}
const runQueue = (queue, iterator, complete) => {
  function next(index) {
    if (index >= queue.length) {
      return complete();
    }
    let hook = queue[index]; // 取出hook钩子函数
    iterator(hook, () => next(index + 1)); // 就是遍历的过程
  }
  next(0);
}

// 这个 current 就是一个普通的变量,希望 current 变化了可以更新视图
export default class History {
  constructor(router) {
    this.router = router;
    // 这个代表的是,当前路径匹配出来的记录
    // /about/a 匹配=> {path:'/about',component:about}、{path:'/about/A',component:A}
    // this.current = {path:'/',matched:[]}
    this.current = createRoute(null, { path: '/' }); // 默认值
  }
  transitionTo(location, complete) {
    // 获取当前路径匹配出对应的记录,当路径变化时获取对应的记录 => 渲染页面 (router-view实现的)
    // 通过路径拿到对应的记录,有了记录之后,就可以找到对象的匹配
    // 当路径变化后 current 属性会进行更新操作
    let current = this.router.match(location);

    // 防止重复点击 不需要再次渲染
    // 匹配到的个数和路径都是相同的,就不需要再次跳转了
    if (this.current.path == location && this.current.matched.length === current.matched.length) {
      return;
    }

    let queue = this.router.beforeHooks; // 钩子函数队列
    const iterator = (hook, next) => { hook(current, this.current, next) };
    runQueue(queue, iterator, () => {
      this.current = current; // 这个 current 只是响应式的,他的变化不会更新 _route
      this.cb && this.cb(current); // 手动更新 _route 的回调函数
      complete && complete(); // 监听浏览器 url 变化的事件的初始化函数
    });
  }
  listen(cb) { // 保存回调函数
    this.cb = cb;
  }
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 调用 init 时机
  2. 2. init 方法
  3. 3. transitionTo