vue-router(3.5.1)源码梳理

如何理解vue-router源码?

vue-router 源码分析
date: 2021-03-26
1. import Vue VueRouter 进来
2. 使用Vue.use(VueRouter) 来进行router的install
                                              -> 使用mixin混入每个组件钩子“beforeCreate”&“destroyed”
                                              -> Object.defineProperty定义拦截“$router”&"$route"
                                              -> Vue.component注册“router-view” & "router-link"
                                              -> 挂载组件上钩子函数(守卫)‘beforeRouterEnter’ & ‘beforeRouteLeave’ & ‘beforeRouteUpdate’
3. 执行new VueRouter里面逻辑
                           -> 根据用户定义的routes构建匹配器
                           -> 根据mode进行不同模式进行初始化
4. 若传入了router给实例
                       -> 则触发组件里面beforeCreated钩子中判断 isDef(this.$options.router)
                       -> 触发VueRouter的init 初始化方法 且调用transitionTo方法来让路由切换
                       -> 内部继续调用confirmTransition方法 执行真正切换
/**1. 通过插件方式安装vue-router */
// 源码 src/install.js

// 引入官方组件 router-view router-link
import View from './components/view';
import Link from './components/link';

export let _Vue;

export function install(Vue) {
   
  // TODO:1. 这里作一次 已经安装过的时候 直接返回
  if (install.installed && _Vue === Vue) return;
  install.installed = true;

  _Vue = Vue;

  const isDef = v => v !== undefined;

  const registerInstance = (vm, callVal) => {
   
    let i = vm.$options._parentVnode;
    if (isDef(i) && isDef((i = i.data)) && isDef((i = i.registerRouteInstance))) {
   
      i(vm, callVal);
    }
  };

  // TODO:2. 这个地方通过插件install方法传入Vue实例
  // 再通过Vue.mixin 混入beforeCreate&destroyed两个钩子函数
  // 这个地方我们需要注意 这里是在我们进行mixin后 那么后面的所有子组件都会有这两个钩子函数 将这两者压入options里面
  Vue.mixin({
   
    beforeCreate() {
   
      // 挂载到所有组件中 每次来判断 $options里面是否有router值
      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 {
   
        // 若莫得 若其父级存在 则赋值_routerRoot 若均无 则直接给vue实例
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
      }
      // TODO: 注册实例
      registerInstance(this, this);
    },
    destroyed() {
   
      registerInstance(this);
    },
  });

  // TODO: 3. 这个地方也很关键 就是在Vue.prototype原型上 设置$router 及$route属性的 getter方法重写
  // 所以我们经常在代码中使用this.$router this.$route 这里就是映射到vue-router的实例属性
  Object.defineProperty(Vue.prototype, '$router', {
   
    get() {
   
      return this._routerRoot._router;
    },
  });

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

  // TODO: 4. 这个里面通过Vue全局注册了router-view router-link的组件
  // 所以我们经常在代码中可以随意使用这两个组件
  Vue.component('RouterView', View); // router-view 这个组件是个函数式组件
  Vue.component('RouterLink', Link);

  const strats = Vue.config.optionMergeStrategies;
  // use the same hook merging strategy for route hooks
  // TODO: 5. 这个地方是获取到Vue配置项所有方法 并且将路由的组件钩子挂载到Vue上面
  // 因此 我们经常在代码里面使用 组件内路由钩子函数
  // beforeRouterEnter
  // beforeRouteLeave
  // beforeRouteUpdate
  // 与created 对应的mergeHook一致
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
}
/**2. 初始化 */
// 源码 src/index.js
var VueRouter = function VueRouter(options) {
   
  if (options === void 0) options = {
   };

  this.app = null;
  this.apps = [];
  this.options = options;
  this.beforeHooks = [];
  this.resolveHooks = [];
  this.afterHooks = [];
  // TODO: 这里根据我们传入的routes 路由匹配器 并且在new VueRouter的时候就去做了这件事
  this.matcher = createMatcher(options.routes || [], this);
  // TODO: 这里设置路由模式 默认为hash模式
  var mode = options.mode || 'hash';
  // TODO: 这里当设置为history模式时 但是不支持history.pushState时 根据fallback来决定是否回退到hash模式
  this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
  if (this.fallback) {
   
    mode = 'hash';
  }
  // TODO:如果不是浏览器环境 则直接启用abstract模式路由
  if (!inBrowser) {
   
    mode = 'abstract';
  }
  this.mode = mode;
  // TODO: 这里会根据mode的不同 去分别创建对应的history模式实例 后面分别来分析下这些类分别做了些啥
  // 这里所有得模式都是基于History基类 下面先了解下History基类
  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);
    }
  }
};
// 2.1 根据路由配置项 来创建匹配器
// [
//   {
   
//     path: "/",
//     redirect: "/login"
//   },
//   {
   
//     path: "/home",
//     component: () => import("Layout/"),
//     redirect: "/",
//     children: [
//       {
   
//         path: "/",
//         name: "home",
//         component: () => import("Pages/Home/"),
//         meta: {
   
//           index: 1,
//           title: "xxx",
//           isBackAppRouter: true
//         }
//       }
//     ]
//   }
// ]
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(parentOrRoute, route) {
   
    var parent = typeof parentOrRoute !== 'object' ? nameMap[parentOrRoute] : undefined;
    // $flow-disable-line
    createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent);

    // add aliases of parent
    if (parent) {
   
      createRouteMap(
        // $flow-disable-line route is defined if parent is
        parent.alias.map(function (alias) {
   
          return {
    path: alias, children: [route] };
        }),
        pathList,
        pathMap,
        nameMap,
        parent
      );
    }
  }

  function getRoutes() {
   
    return pathList.map(function (path) {
   
      return pathMap[path];
    });
  }

  function match(raw, currentRoute, redirectedFrom) {
   
    var location = normalizeLocation(raw, currentRoute, false, router);
    var name = location.name;

    if (name) {
   
      var record = nameMap[name];
      {
   
        warn(record, "Route with name '" + name + "' does not exist");
      }
      if (!record) {
   
        return _createRoute(null, location);
      }
      var paramNames = record.regex.keys
        .filter(function (key) {
   
          return !key.optional;
        })
        .map(function (key) {
   
          return key.name;
        });

      if (typeof location.params !== 'object') {
   
        location.params = {
   };
      }

      if (currentRoute && typeof currentRoute.params === 'object') {
   
        for (var key in currentRoute.params) {
   
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
   
            location.params[key] = currentRoute.params[key];
          }
        }
      }

      location.path = fillParams(record.path, location.params, 'named route "' + name + '"');
      return _createRoute(record, location, redirectedFrom);
    } else if (location.path) {
   
      location.params = {
   };
      for (var i = 0; i < pathList.length; i++) {
   
        var path = pathList[i];
        var record$1 = pathMap[path];
        if (matchRoute(record$1.regex, location.path, location.params)) {
   
          return _createRoute(record$1, location, redirectedFrom);
        }
      }
    }
    // no match
    return _createRoute(null, location);
  }

  function redirect(record, location) {
   
    var originalRedirect = record.redirect;
    var redirect = typeof originalRedirect === 'function' ? originalRedirect(createRoute(record, location, null, router)) : originalRedirect;

    if (typeof redirect === 'string') {
   
      redirect = {
    path: redirect };
    }

    if (!redirect || typeof redirect !== 'object') {
   
      {
   
        warn(false, 'invalid redirect option: ' + JSON.stringify(redirect));
      }
      return _createRoute(null, location);
    }

    var re = redirect;
    var name = re.name;
    var path = re.path;
    var query = location.query;
    var hash = location.hash;
    var params = location.params;
    query = re.hasOwnProperty('query') ? re.query : query;
    hash = re.hasOwnProperty('hash') ? re.hash : hash;
    params = re.hasOwnProperty('params') ? re.params : params;

    if (name) {
   
      // resolved named direct
      var targetRecord = nameMap[name];
      {
   
        assert(targetRecord, 'redirect failed: named route "' + name + '" not found.');
      }
      return match(
        {
   
          _normalized: true,
          name: name,
          query: query,
          hash: hash,
          params: params,
        },
        undefined,
        location
      );
    } else if (path) {
   
      // 1. resolve relative redirect
      var rawPath = resolveRecordPath(path, record);
      // 2. resolve params
      var resolvedPath = fillParams(rawPath, params, 'redirect route with path "' + rawPath + '"');
      // 3. rematch with existing query and hash
      return match(
        {
   
          _normalized: true,
          path: resolvedPath,
          query: query,
          hash: hash,
        },
        undefined,
        location
      );
    } else {
   
      {
   
        warn(false, 'invalid redirect option: ' + JSON.stringify(redirect));
      }
      return _createRoute(null, location);
    }
  }

  function alias(record, location, matchAs) {
   
    var aliasedPath = fillParams(matchAs, location.params, 'aliased route with path "' + matchAs + '"');
    var aliasedMatch = match({
   
      _normalized: true,
      path: aliasedPath,
    });
    if (aliasedMatch) {
   
      var matched = aliasedMatch.matched;
      var aliasedRecord = matched[matched.length - 1];
      location.params = aliasedMatch.params;
      return _createRoute(aliasedRecord, location);
    }
    return _createRoute(null, location);
  }

  function _createRoute(record, location, redirectedFrom) {
   
    if (record && record.redirect) {
   
      return redirect(record, redirectedFrom || location);
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值