欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue-Router 源码分析(二) 设计思想及代码结构

程序员文章站 2022-10-30 17:58:07
根据VueRouter的执行流程,可以通过这三个步骤来理解它的设计思想: 第一步:我们new VueRouter创建VueRouter实例的时候会通过深度遍历把传入的router属性对应的数组给解析一下,保存到一个Map中,每个Map对应router的一个元素,我们称之为路由记录,解析的时候会给每个 ......

根据vuerouter的执行流程,可以通过这三个步骤来理解它的设计思想:

  • 第一步:我们new vuerouter创建vuerouter实例的时候会通过深度遍历把传入的router属性对应的数组给解析一下,保存到一个map中,每个map对应router的一个元素,我们称之为路由记录,解析的时候会给每个路由记录增加一个正则表达式,用于<vuerouter-link>组件在渲染时查找哪个路由记录可以与之匹配
  • 第二步:解析<router-link></router-link>组件的时候首先获取名为to的props(例如<router-link to="/login"><a>登陆</a></router-link>里的to属性),然后会遍历第一步map里的每个记录(路由记录),并用路由记录中的正则去匹配这个to值,看看该正则是否能匹配,如果能匹配则表示可以渲染这个路由记录对应的组件了,然后通过vue内部的$createelement全局函数渲染一个原生dom标签(默认为a标签,可以通过<router-link/>组件的tag这个props去修改它),渲染后会在这个dom对象上绑定一个click事件,当click事件触发时,会调用vuerouter实例的push()方法去修改路由,注意,vuerouter是通过click事件来触发路由的,不是通过a的href属性来触发,如果通过event这个props传入了其它事件,则也会进行绑定
  • 第三步:就是渲染<router-view><router-view/>组件了,它首先会获取当前<router-view></router-view>相对于最顶层的大vue实例所嵌套的深度的层次数,然后this.$route.matched上保存了所有父链路由对应的component信息的(this.$route.matched是当前路由记录对应的所有嵌套路劲片段的路由记录,也就是所有父路由对象都在这个数组里面,包含了当前页面的路由信息),这样就可以通过这个对象获取当前需要渲染的组件了,this.$route对象是个响应式数据,根据不同的页面,都会变动的, 后面会再单独讲解一下$router和$route的区别

vuerouter里的路由有三种模式:

  • hash         使用 url hash 值来作路由 
  • history      依赖 html5 history api 和服务器配置
  • abstract  支持所有 javascript 运行环境,如 node.js 服务器端

 writer by:大沙漠 qq:22969969

这三种模式有很多共同点,所以vuerouter在实现的时候定义了一个history基类,在基类上定义共同方法,然后再定义三个子类,分别继承整个history类来实现,如下:

class history {
    //定义一些公共方法,例如跳转操作
}

class hashhistory extends history {
    //hash模式的差异实现,例如获取hash值,设置hash值
}

class html5history extends history {
    //html5模式的差异实现,例如获取当前地址,设置当前地址
}

class abstracthistory extends history {
    //abstract模式的差异实现
}

vuerouter会根据当前的模式,来创建一个对应的history的实例,再在整个实例上进行一系列操作

vuerouter的加载过程

在vuerouter加载的时候,对于浏览器环境它会执行vue.use(vuerouter)自动进行安装,如下:

if (inbrowser && window.vue) {    //如果在浏览器环境下且window.vue存在
  window.vue.use(vuerouter);        //则调用window.vue.use安装vuerouter,此时就会执行install方法
}

vue插件安装的时候就会执行对应的install方法,与加载有关的如下:

    function install(vue) {                                 //vue-router的安装方法
        /*略*/
        vue.mixin({                                             //混入生命周期函数 注意函数内的上下文是vue实例
            beforecreate: function beforecreate() {                 //beforecreate生命周期函数
                    if (isdef(this.$options.router)) {                  //如果this.$options.router存在    ;就是在创建vue实例时传入的router对象
                        this._routerroot = this;                            //在vue实例上添加一个_routerroot指向自己,即vue实例
                        this._router = this.$options.router;                //在vue实例上添加一个_router指向构造时的vue-router实例
                        this._router.init(this);                            //vue-router实例调用init()进行初始化,参数为vue实例
                        vue.util.definereactive(this, '_route', this._router.history.current);  //通过vue的definereactive把_router变成响应式,等于this._router.history.current
                    } else {                                            //非根组件
                        this._routerroot = (this.$parent && this.$parent._routerroot) || this; //如果this.$options.router则设置$this._routerroot为占位符节点的_routerroot,这样就可以访问到vue-rooter实例了
                    }
                    registerinstance(this, this);
                },
            destroyed: function destroyed() {                       //销毁生命周期函数
                registerinstance(this);
            }
        });
        /*略*/
        vue.component('routerview', view);                      //注册routerview组件
        vue.component('routerlink', link);                      //注册routerlink逐渐
 
    }

我们可以看到通过mixin混入在vue的beforecreate生命周期函数内插入了一段代码,会通过this.$options.router.init(this)执行初始化代码(this.$options.router就是我们执行new vue()时传入的vuerouter实例),这样就会执行vuerouter的init()方法了,另外还会执行vue.component()方法将routerview和routerlink组件注册为全局组件

vuerouter的init方法就是初始化路由,如果当前页面没有路由(例如http://test.com/)则初始化为根地址/(例如http://test.com/#/),就是执行基类的transitionto方法进行跳转的过程,具体就不贴代码了,一贴就太多代码了。

vuerouter的执行过程

我们以上一篇文章https://www.cnblogs.com/greatdesert/p/12398443.html为例,依次整理一下vuerouter的执行过程

上面是加载的过程,对于例子里们我们执行new vuerouter的时候:

    const routes = [                                                         //定义路由指向
        {path:'/login',component:login},
        {path:'/hello',name:'user',component:hello},
        {path:'/info/:id',component:info},
    ]

    var router = new vuerouter({                                             //创建一个vuerouter实例
        routes
    })

会执行vue-router的构造函数:

var vuerouter = function vuerouter (options) {         //构造函数
  if ( options === void 0 ) options = {};                 //如果option为undefined,则修正为空对象

  this.app = null;
  this.apps = [];
  this.options = options;
  this.beforehooks = [];
  this.resolvehooks = [];
  this.afterhooks = [];
  this.matcher = creatematcher(options.routes || [], this);         //将路由信息转换为一个对象信息,返回一个对象,含有match和addroutes属性,分别对应两个函数

  //初始化/修正mode
  var mode = options.mode || 'hash';                                                         //如果没有传mode,则默认为hash模式
  this.fallback = mode === 'history' && !supportspushstate && options.fallback !== false;     //如果当前为history模式,但是浏览器不支持pushstate,则fallback不为false 则设置fallback为true
  if (this.fallback) {
    mode = 'hash';
  }
  if (!inbrowser) {
    mode = 'abstract';
  }
  this.mode = mode;

  switch (mode) {                                                 //根据不同的模式,对this.history做出实例化
    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:
      {
        assert(false, ("invalid mode: " + mode));
      }
  }
};

creatematcher函数会解析我们传入的router值,该函数逻辑如下:

function creatematcher (routes,router) {    
  var ref = createroutemap(routes);                              //创建一个map,就是上面第一步说的路由记录
  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) {/**/}
  function redirect (record,location) { /**/ }

  function alias (record,location,matchas) { /**/ }
  function _createroute (record,location,redirectedfrom) { /**/ }

  return {match: match,addroutes: addroutes}                 //返回这一个对象,我们可以通过直接执行这两个键对应的函数来匹配路由记录
}

对于例子里来说,执行到这里创建的路由记录如下:

Vue-Router 源码分析(二)  设计思想及代码结构

namemap是给命名路由用的,pathlist是存储所有的路劲,pathmap才是所有路由记录的地方。

有了路由记录,vue-link和vue-view组件就可以大展手脚了,这两个组件后面再单独详解