vue router 源码

一、vue router安装

import VueRouter from 'vue-router'
Vue.use(VueRouter)

我们知道vue的use函数

Vue.use = function (plugin) {
    ......
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args);
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args);
    }

    installedPlugins.push(plugin);
    return this
};

看上面代码我们知道,需要到plugin中去找install函数,下面我们找到vue router的源码,找到install函数

function install (Vue) {
  ......
  Vue.mixin({
    beforeCreate: function beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this;
        this._router = this.$options.router;
        this._router.init(this);
        Vue.util.defineReactive(this, '_route', this._router.history.current);
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
      }
      registerInstance(this, this);
    },
    destroyed: function destroyed () {
      registerInstance(this);
    }
  });

  Object.defineProperty(Vue.prototype, '$router', {
    get: function get () { return this._routerRoot._router }
  });

  Object.defineProperty(Vue.prototype, '$route', {
    get: function get () { return this._routerRoot._route }
  });

  Vue.component('RouterView', View);
  Vue.component('RouterLink', Link);
  ......
}

. 上面this._router.init(this); this指的是全局的Vue函数

上面我们看到当安装vue router时,会加载install函数,这个函数中写有Vue.mixin()的函数,里面有beforeCreate和destroyed,在安装的同时,
全局混入钩子函数 每个组件都会有这些钩子函数,执行就会走这里的逻辑,beforeCreate和destroyed就写入了vue的组件装载中,我们可以随时调用router或route查看相关路由信息

全局注册 router-view,router-link组件

 Vue.component('RouterView', View);
 Vue.component('RouterLink', Link);

这里是用来安装路由组件的

上面的安装函数中我们看到this._router.init(this)这个函数,实际上就是VueRouter.prototype.init的函数

VueRouter.prototype.init = function init (app /* Vue component instance */) {
   ......
  this.apps.push(app);

  // set up app destroyed handler
  // https://github.com/vuejs/vue-router/issues/2639
  app.$once('hook:destroyed', function () {
    // clean out app from this.apps array once destroyed
    var index = this$1.apps.indexOf(app);
    if (index > -1) { this$1.apps.splice(index, 1); }
    // ensure we still have a main app or null if no apps
    // we do not release the router so it can be reused
    if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }

    if (!this$1.app) { this$1.history.teardown(); }
  });


  // main app previously initialized
  // return as we don't need to set up new history listener
  if (this.app) {
    return
  }

  this.app = app;

  var history = this.history;

  if (history instanceof HTML5History || history instanceof HashHistory) {
    ......
    history.transitionTo(
      history.getCurrentLocation(),
      setupListeners,
      setupListeners
    );
  }
 ......
};

注意:

1. app 实际上就是我们项目的vue的实例对象,一般单页面website这里只有一个app

2.由HTML5History实现路由的transitionTo函数转化

二. VueRouter的函数

var VueRouter = function VueRouter (options) {
  ......
  this.app = null;
  this.apps = [];
  ......
  switch (mode) {
    case 'history':
      this.history = new HTML5History(this, options.base);
      break
    case 'hash':
      this.history = new HashHistory(this, options.base, this.fallback);
      break
    case 'abstract':
      this.history = new AbstractHistory(this, options.base);
      break
    default:
      if (process.env.NODE_ENV !== 'production') {
        assert(false, ("invalid mode: " + mode));
      }
  }
};

VueRouter的函数需理解:

1.app就是项目中的vue实例

2.路由的模式:

. history:依赖 HTML5 History API 和服务器配置。
. hash:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器
. abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。**如果发现没有浏览器的 API,路由会自动强制进入这个模式。

三、matcher

在VueRouter中我们可以看到createMatcher来处理routes

var VueRouter = function VueRouter (options) {
  ......
  this.matcher = createMatcher(options.routes || [], this);
  ......
}

查看createMatcher函数

function createMatcher (
  routes,
  router
) {
  var ref = createRouteMap(routes);
  var pathList = ref.pathList;
  var pathMap = ref.pathMap;
  var nameMap = ref.nameMap;

  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap);
  }

  function match (
    raw,
    currentRoute,
    redirectedFrom
  ) {}
}

我们可以看到createMatcher接收两个参数:
1、routes也就是页面中我们配置路径时的数组
2、router也就是我们的router的实例,new VueRouter()创建的实例
重要的三个函数是:

1. createRouteMap:根据路由的配置描述建立映射表,包括路径、名称到路由 record 的映射关系, 最重要的就是 createRouteMap 这个方法,这里也是动态路由匹配和嵌套路由的原理

2. addRoutes:动态添加路由配置

3. match:根据传入的 raw 和当前的路径 currentRoute 计算出一个新的路径并返回

createRouteMap---路由映射处理

function createRouteMap (
  routes,
  oldPathList,
  oldPathMap,
  oldNameMap
) {
  ......
  // the path list is used to control path matching priority
  var pathList = oldPathList || [];
  // $flow-disable-line
  var pathMap = oldPathMap || Object.create(null);
  // $flow-disable-line
  var nameMap = oldNameMap || Object.create(null);

  routes.forEach(function (route) {
    addRouteRecord(pathList, pathMap, nameMap, route);
  });
  return {
    pathList: pathList,
    pathMap: pathMap,
    nameMap: nameMap
  }
}

处理路由映射pathList,pathMap,nameMap

addRoutes--增加动态路由处理

这是在 vue2.2.0 之后新添加的 api ,或许很多情况路由并不是写死的,需要动态添加路由。我们只需要传入 routes 即可,他就能在原基础上修改

function createMatcher (
  routes,
  router
) {
  ......
  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap);
  }
  ......
}

match

function match (
    raw,
    currentRoute,
    redirectedFrom
  ) {
  ......
  return _createRoute(null, location)
}

raw 是 RawLocation 类型,它可以是一个 url 字符串,也可以是一个 Location 对象;currentRoute 是 Route 类型,它表示当前的路径;redirectedFrom 和重定向相关。
match 方法返回的是一个路径,它的作用是根据传入的 raw 和当前的路径 currentRoute 计算出一个新的路径并返回

四、路由切换

History.prototype.transitionTo = function transitionTo (
  location,
  onComplete,
  onAbort
) {
 ......
  try {
    route = this.router.match(location, this.current);
  } catch (e) {
   ......
  }
 ......
  this.confirmTransition(
    route,
    function () {
      // 更换路由然后更新updateRoute
      this$1.updateRoute(route);
      onComplete && onComplete(route);
      this$1.ensureURL();
      this$1.router.afterHooks.forEach(function (hook) {
        hook && hook(route, prev);
      });
    },
    ......
  );
};

接下来查看confirmTransition函数

History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) {
  ......
          if (typeof to === 'object' && to.replace) {
            this$1.replace(to);
          } else {
            this$1.push(to);
          }
  ......
};

查看push

  HTML5History.prototype.push = function push (location, onComplete, onAbort) {
....
    this.transitionTo(location, function (route) {
      pushState(cleanPath(this$1.base + route.fullPath));
      ......
    }, onAbort);
  };

查看pushState

function pushState (url, replace) {
......
  try {
    if (replace) {
    ......
    } else {
      history.pushState({ key: setStateKey(genStateKey()) }, '', url);
    }
  } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url);
  }
}

可以看到最终使用的是history.pushState

路径切换方法 transitionTo,接着在回调函数中调用pushHash方法,这个方法调用的 pushState 方法底层是调用了浏览器原生 history 的方法。push 和 replace 的区别就在于一个将 url 推入了历史栈,一个没有,最直观的体现就是 replace 模式下浏览器点击后退不会回到上一个路由去 ,另一个则可以。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容