vue-router浅析

在进行路由创建时,我们需要一个router对所有的路由进行管理工作以及确认采取哪种路由模式。 route为路由对象,它包括path、component、name等

new Router

new router时进行matcher的创建和路由模式的确定
history采用浏览器历史调用栈的方式、 hash模式则使用带#URL格式

var VueRouter = function VueRouter (options) {
  if ( options === void 0 ) options = {};

  this.app = null;
  this.apps = [];
  this.options = options;
  this.beforeHooks = [];
  this.resolveHooks = [];
  this.afterHooks = [];
  // matcher对象包含用于路由匹配的方法、还有addRoutes添加路由方法
  // 调用该方法里会 进行createRouteMap方法的调用, 生成routeMap
  this.matcher = createMatcher(options.routes || [], this);

  var mode = options.mode || 'hash';
  this.fallback =
    mode === 'history' && !supportsPushState && options.fallback !== false;
  if (this.fallback) {
    mode = 'hash';
  }
  if (!inBrowser) {
    mode = 'abstract';
  }
  this.mode = mode;

  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));
      }
  }
};

createRouteMap

以下面一个简单的示例为例

const Foo = { template: '
foo
' } const Bar = { template: '
bar
' } const Sub = { template: '
Sub
' } ... const routes = [ { path: '/foo', component: Foo , children: [ { path: '/sub', component: Sub } ]}, { path: '/bar', component: Bar } ]

经过createRouteMap后返回的结果如下


router-ceateMap.png

createRouteMap将路由对象进行了拍平处理, 生成pathMap映射。
path属性对应当前的路由路径
components当前路由对应的模板
regex进行路径的匹配工作,同时对动态路由进行params的获取

全局挂载

我们知道在使用$router时可获取到router实例对象, 其实现的代码如下
通过beforeCreate在Vue组件上混入_routerRoot根路由, 方法$router时实际上是通过this._routerRoot._router获取到的。

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 }
  });

思考一下在进行路由切换的时候经历了一些什么, 略去细节的话大体如下
1)比如我们会先点击一个link,
2)link组件会触发handler方法
3)进行当前路由属性的变更,
4)路由的变更最终引起view视图更新工作。
更具体一些的实现如下

RouterLink组件

绑定事件方法如下

var handler = function (e) {
      if (guardEvent(e)) {
        if (this$1.replace) {
          router.replace(location, noop);
        } else {
          router.push(location, noop);
        }
      }
    };

以history模式为例
其中router.push的调用轨迹 router.push -> history.push -> transitionTo -> confirmTransition
-> runQueue -> onComplete -> updateRoute -> this.cb(route) -> app._route = route;

  1. router.push中进行了history.push的调用工作。
  2. history.push获取当前的路由对象作为fromRoute,可在导航守卫中进行使用
  3. transitionTo为路由切换的入口函数, 不管是link点击、history前进后退或者直接调用push方法最终都是以该方法作为导航切换的入口。
    在该函数中会进行route对象的匹配获取, 然后进入到confirmTransition方法
  4. confirmTransition创建调用队列包括 extractLeaveGuards 、beforeHooks、extractUpdateHooks、activated.、resolveAsyncComponents。
    创建一个迭代方法用于队列的调用,队列事件完成后进行回调函数的处理
  5. onComplete包括 updateRoute、 pushState、afterHooks等方法
  6. updateRoute会调用history.listen中添加的cb方法从而将app上挂载的_route进行更新,触发set监听和获取当前路由。
    pushState最终调用 history.pushState方法向历史堆栈中添加一条状态

RouterView组件

在render的时候从var route = parent.$route;获取当前的route对象, 该组件是函数式组件会从parent中获取route。
_route属性为响应式的, 当它发生变化会触发view视图的更新。

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

_route响应式实现的方法

Vue.util.defineReactive(this, '_route', this._router.history.current);
 var route = parent.$route;
 ...
 history.listen(function (route) {
    this$1.apps.forEach(function (app) {
      app._route = route;
    });
  });

通过defineReactive, 将_route设置为响应式的。 get进行依赖收集, set时触发view更新。

路由事件绑定及变化过程

var eventType = supportsPushState ? 'popstate' : 'hashchange';
    window.addEventListener(
      eventType,
      handleRoutingEvent
    );
    this.listeners.push(function () {
      window.removeEventListener(eventType, handleRoutingEvent);
    });

transitionTo -> (router.match),
updateRoute用于更新当前路由 -> app._route = route;
confirmTransition ->resolveQueue
当活动历史记录条目更改时,将触发popstate事件。需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮
在使用history模式时需考虑404问题。

url的hash发生变更时会触发 hashchange方法

你可能感兴趣的:(vue-router浅析)