【源码解析】vue-router实现原理,原来new VueRouter时做了这么多事

1,112 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

前言

大家好,继续我们的VueRouter源码解读系列。上篇文章VueRouter install那些事中我们主要解读了vue-router中的install方法,从该方法中我们得知其主要目的就是为了让我们在每个组件实例中都能够访问到VueRouter的实例$router和当前的路由对象¥route,并且在每个组件的模板中都能够直接使用VueRouter为我们提供的两个相关组件RouteView和RouterLink。接下来我们再看下当我们通过new VueRouter去创建实例时又做了哪些事情。

创建VueRouter实例

通常我们在创建VueRouter实例时一般会相应的传递三个参数:数组类型的routes也就是路由列表、字符串类型的base表示根路径和字符串类型的mode路由的模式

  • 关于routes就是一个路由列表,该列表中包含了一个个路由对象,其中每个路由对象又包含了path、name、component等相关的路由信息
  • base则表示网站的基路径,默认是"/"
  • mode表示路由模式,路由模式分为三中:默认为hash模式
    • history:依赖 HTML5 History API 和服务器配置,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
    • hash:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器
    • abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式
 const router = new VueRouter({
     base:"/",
     mode:"hash",
     routes:[{path:"/",name:"Home",component:xxx},{path:"/about",name:"About",component:xxx}]
 })

new VueRouter源码解读

如果直观的从VueRouter的构造函数源码来看,当我们在new VueRouter的时候所执行的代码并不是很多,也就四五十行,这么一看貌似好像也没做多少事情,但是事实并非如此。别看就这么仅仅几十行代码,实际上在这个过程中可是做了很多很多事情,因为这里还调用了其它方法,如createMatcher,而在createMatcher中又链式的调用了好多其它方法,如createRouteMap -> addRouteRecord -> normalizePath等等,因此这里我们仅仅把VueRouter的构造函数和createMatcher两个方法详细解读一下,至于其它方法就把大概做了哪些事简单描述一下即可,就不详细展开了。

VueRouter

  var VueRouter = function VueRouter (options) {
    if ( options === void 0 ) options = {};
    {
      warn(this instanceof VueRouter, "Router must be called with the new operator.");
    }
    this.app = null;
    this.apps = [];
    this.options = options;
    this.beforeHooks = [];
    this.resolveHooks = [];
    this.afterHooks = [];
    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:
        {
          assert(false, ("invalid mode: " + mode));
        }
    }
  };
  • 在VueRouter构造函数中,首先会给router实例初始化一些属性:app、apps、options、beforeHooks、resolveHooks和afterHooks等
  • 下面会调用一个createMatcher方法,在该方法内部会定义一些其它函数,最后会返回一个包含了match、addRoutes、addRoute和getRoutes四个方法等对象,关于createMatcher我们将在后面进行详细解读
  • 定义一个mode变量用于保存路由模式,首先检测options中是否有mode,如果有则直接使用,如果没有则默认使用hash模式
  • 给实例添加fallback属性,该属性主要用于指示是否会回退到hash模式,如果指定options中的fallback为true,这时如果mode是history模式,但浏览器不支持H5的相关API,这时就会自动回退到hash模式
  • 如果是非浏览器环境(如node.js)就会自动切换成abstract模式
  • 最后根据不同的路有模式分别创建不同的实例
    • history模式:创建一个HTML5History实例并赋值给路由实例的histroy
    • hash模式:创建一个HashHistory实例并赋值给路由实例的histroy
    • abstract模式:创建一个AbstractHistory实例并赋值给路由实例的histroy

createMatcher

在解读VueRouter构造函数的源码时,我们看到其中还调用了一个名为createMatcher的方法,而在上面我们只是简单提到该方法返回了一个包含了四个方法的对象。下面我们来详细解读下在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 addRoute(){}
    
    function getRoutes(){}
    
    function match(){}
    
    function redirect(){}
    
    function alias(){}
    
    function _createRoute(){}
    
    return {
      match: match,
      addRoute: addRoute,
      getRoutes: getRoutes,
      addRoutes: addRoutes
    }
  }
  • 该方法接收两个参数:routes 创建VueRouter实例传递的路由列表, router 路由实例
  • 在方法内部首先调用了另外一个方法:createRouteMap从名称上来看大概也能猜到,这个方法主要是用来创建路由的映射关系的
    • 在createRouteMap内部首先会创建一个数组pathList用于保存所有路由的路径,两个对象pathMap和nameMap,分别用于保存路由路径与路由对象的映射关系,和路由名称与路由对象的映射关系
    • 然后遍历传进来路由列表routes,并为每个路由对象调用addRouteRecord方法,在该方法内部主要又做了如下几件事:
      • 调用normalizePath方法对传进来的路由path进行标准化处理
      • 创建一个包含path、regex、components、name、alias、redirect和meta等属性的对象
      • 处理嵌套路由,即为当前路由的子路由(children)同样调用addRouteRecord方法
      • 将所有路由的路径path添加的上面创建的数组变量pathList中
      • 以路由路径path为key,上面创建record对象为值,将每个路由的映射关系保存到pathMap对象中
      • 以路由名称name为key,上面创建的record对象为值,将每个路由的映射关系保存的nameMap对象中
    • 在addRouteRecord处理完毕后,这里又利用for循环对pathList中带*的路径进行单独处理,目的就是为了确保这种通配符路由放在最后匹配
    • 最后返回一个包含了pathList、pathMap和nameMap的对象
  • 调用的其它方法都处理完毕后继续回到createMatcher方法内部,一调用已经完成下面就七定义和一返回了
  • addRoutes,该方法是一个对外的api,也就是说在组件中可以通过调用该方法动态添加路由信息,该方法接收一个数组参数,也就是说一次性可以添加多个路由,其实在该方法内部还是调用的createRouteMap
  • addRoute,跟上面的方法有着同样的功能,也是动态添加路由信息的,不过这个方法一次只能添加一条路由
  • getRoutes,对外的api,获取全部的路由信息
  • match,根据路由路径或路由名称匹配路由
  • redirect,路由重定向
  • alias, 路由别名
  • _createRoute,创建路由
  • 最后将addRoutes,addRoute,getRoutes和match组合成对象返回 caoshenhuinan.gif

总结

本次分享我们解读了VueRouter构造函数的源码,表面上看只是简短的几十行代码却延伸出了很多方法,简单总结如下:

通过createMatcher调用createRouteMap创建路由映射关系,定义对外的能够动态添加路由的两个方法addRoutes和addRoute,获取全部路由的getRoutes和匹配路由信息的match方法,然后处理路由模式,浏览器环境下默认是hash模式,如果是history模式但由于浏览器不支持H5相关API并且允许回退的情况下则会自动回退的hash模式,非浏览器环境下则使用abstract模式,最后根据不同的模式创建相关的实例

好了关于VueRouter创建实例相关的源码就分享到这里了,欢迎大佬们多多指点!