Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung der Vue-Router-Quellcode-Beispiele

Detaillierte Erläuterung der Vue-Router-Quellcode-Beispiele

小云云
小云云Original
2018-02-07 14:47:461697Durchsuche

Dieser Artikel vermittelt Ihnen hauptsächlich das Lesen und Lernen des Vue-Router-Quellcodes. Genau wie bei der Analyse des Vue-Routers verstehen wir zunächst anhand eines einfachen Beispiels, wie Vue-Router verwendet wird, und analysieren dann, wie es funktioniert Ich hoffe, dass es im Quellcode implementiert ist. Kann allen helfen.

Beispiel

Das folgende Beispiel stammt aus example/basica/app.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const Home = { template: &#39;<div>home</div>&#39; }
const Foo = { template: &#39;<div>foo</div>&#39; }
const Bar = { template: &#39;<div>bar</div>&#39; }

const router = new VueRouter({
  mode: &#39;history&#39;,
  base: __dirname,
  routes: [
    { path: &#39;/&#39;, component: Home },
    { path: &#39;/foo&#39;, component: Foo },
    { path: &#39;/bar&#39;, component: Bar }
  ]
})

new Vue({
  router,
  template: `    <div id="app">      <h1>Basic</h1>      <ul>        <li><router-link to="/">/</router-link></li>        <li><router-link to="/foo">/foo</router-link></li>        <li><router-link to="/bar">/bar</router-link></li>        <router-link tag="li" to="/bar" :event="[&#39;mousedown&#39;, &#39;touchstart&#39;]">          <a>/bar</a>        </router-link>      </ul>      <router-view class="view"></router-view>    </div>
  `
}).$mount(&#39;#app&#39;)

Erster Aufruf von Vue.use(VueRouter). Die Methode Vue.use() wird von Vue zum Installieren von Plug-Ins verwendet. Dann wird VueRouter instanziiert. Schauen wir uns an, was der VueRouter-Konstruktor tut.
Beginnen Sie mit der Quellcode-Eintragsdatei src/index.js

import type { Matcher } from &#39;./create-matcher&#39;export default class VueRouter {  constructor (options: RouterOptions = {}) {    this.app = null    this.apps = []    this.options = options    this.beforeHooks = []    this.resolveHooks = []    this.afterHooks = []    this.matcher = createMatcher(options.routes || [], this)    let mode = options.mode || &#39;hash&#39;    this.fallback = mode === &#39;history&#39; && !supportsPushState && options.fallback !== false    if (this.fallback) {
      mode = &#39;hash&#39;
    }    if (!inBrowser) {
      mode = &#39;abstract&#39;
    }    this.mode = mode    switch (mode) {      case &#39;history&#39;:        this.history = new HTML5History(this, options.base)        break      case &#39;hash&#39;:        this.history = new HashHistory(this, options.base, this.fallback)        break      case &#39;abstract&#39;:        this.history = new AbstractHistory(this, options.base)        break      default:        if (process.env.NODE_ENV !== &#39;production&#39;) {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }

  init (app: any /* Vue component instance */) {    this.apps.push(app)    // main app already initialized.    if (this.app) {      return
    }    this.app = app    const history = this.history    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route => {      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

  getMatchedComponents (to?: RawLocation | Route): Array<any> {    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute    if (!route) {      return []
    }    return [].concat.apply([], route.matched.map(m => {      return Object.keys(m.components).map(key => {        return m.components[key]
      })
    }))
  }

}

Sehen Sie sich den Code Schritt für Schritt an. Beginnen wir mit der Konstruktorfunktion. Um sie zu implementieren, schauen wir uns zunächst an, was diese Initialisierungsbedingungen darstellen

  • this.app stellt die aktuelle Vue-Instanz dar

  • this.apps repräsentiert alle App-Komponenten

  • this.options repräsentiert die Optionen des eingehenden VueRouter

  • this.resolveHooks repräsentiert die Array von Resolve-Hook-Callback-Funktionen, Resolution wird verwendet, um den Zielort zu analysieren

  • this.matcher erstellt eine Matching-Funktion

Es gibt einen createMatcher ()-Funktion im Code, werfen wir einen Blick darauf. Implementierung

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
  ) {    var location = normalizeLocation(raw, currentRoute, false, router);    var name = location.name;    // 命名路由处理    if (name) {      // nameMap[name]的路由记录      var record = nameMap[name];
      ...
        location.path = fillParams(record.path, location.params, ("named route \"" + name + "\""));        // _createRoute用于创建路由        return _createRoute(record, location, redirectedFrom)
    } else if (location.path) {      // 普通路由处理
    }    // no match    return _createRoute(null, location)
  }  return {
    match: match,
    addRoutes: addRoutes
  }
}

createMatcher() hat zwei Parameter, Routen, die die beim Erstellen des VueRouter übergebenen Routenkonfigurationsinformationen darstellen, und Router, der die VueRouter-Instanz darstellt. Die Funktion von createMatcher() besteht darin, über createRouteMap die entsprechende Karte für die eingehenden Routen und eine Methode zum Erstellen der Karte zu erstellen.
Sehen wir uns zunächst die Definition der Methode createRouteMap() an

function createRouteMap (
  routes,
  oldPathList,
  oldPathMap,
  oldNameMap) {  // 用于控制匹配优先级  var pathList = oldPathList || [];  // name 路由 map  var pathMap = oldPathMap || Object.create(null);  // name 路由 map  var nameMap = oldNameMap || Object.create(null);  // 遍历路由配置对象增加路由记录
  routes.forEach(function (route) {
    addRouteRecord(pathList, pathMap, nameMap, route);
  });  // 确保通配符总是在pathList的最后,保证最后匹配  for (var i = 0, l = pathList.length; i < l; i++) {    if (pathList[i] === &#39;*&#39;) {
      pathList.push(pathList.splice(i, 1)[0]);
      l--;
      i--;
    }
  }  return {    pathList: pathList,    pathMap: pathMap,    nameMap: nameMap
  }
}

createRouteMap() hat 4 Parameter: Routen Stellt die Konfigurationsinformationen dar, oldPathList enthält ein Array aller Pfade für übereinstimmende Prioritäten, oldNameMap stellt die Namenszuordnung dar und oldPathMap stellt die Pfadzuordnung dar. createRouteMap dient zum Aktualisieren von pathList, nameMap und pathMap. Was stellt nameMap dar? Es handelt sich um ein Objekt, das Routing-Datensätze enthält. Jeder Attributwertname ist der Pfadattributwert jedes Datensatzes, und der Attributwert ist der Routing-Datensatz mit diesem Pfadattributwert. Es gibt so etwas wie einen Routing-Datensatz. Was bedeutet das? Der Routing-Datensatz ist eine Kopie des Objekts im Routenkonfigurationsarray (und im untergeordneten Array). Die Routing-Datensätze sind im übereinstimmenden Attribut enthalten, z. B.

const router = new VueRouter({  routes: [    // 下面的对象就是 route record
    { path: &#39;/foo&#39;, component: Foo,      children: [        // 这也是个 route record
        { path: &#39;bar&#39;, component: Bar }
      ]
    }
  ]
})

Im obigen Code wird ein Codeabschnitt verwendet, um Routing-Datensätze zu jeder Route hinzuzufügen. Das Folgende ist die Implementierung von addRouteReord() >

function addRouteRecord (
  pathList,
  pathMap,
  nameMap,
  route,
  parent,
  matchAs
) {  var path = route.path;  var name = route.name;  var normalizedPath = normalizePath(
    path,    parent
  );  var record = {
    path: normalizedPath,
    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
    components: route.components || { default: route.component },
    instances: {},
    name: name,    parent: parent,
    matchAs: matchAs,
    redirect: route.redirect,
    beforeEnter: route.beforeEnter,
    meta: route.meta || {},
    props: route.props == null
      ? {}
      : route.components
        ? route.props
        : { default: route.props }
  };  if (route.children) {
    route.children.forEach(function (child) {
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
    });
  }  if (route.alias !== undefined) {    // 如果有别名的情况
  }  if (!pathMap[record.path]) {
    pathList.push(record.path);
    pathMap[record.path] = record;
  }
}
Ich bin zu faul zu sagen, was die Parameter der Funktion addRouteRecord() bedeuten. Das neu hinzugefügte übergeordnete Element repräsentiert auch die Route Erfassen Sie zunächst den Pfad und den Namen. Standardisieren Sie dann das Format über normalizePath(), erstellen Sie dann das Datensatzobjekt und durchlaufen Sie dann die Unterelemente der Route, um Routing-Datensätze hinzuzufügen. Wenn Aliase vorhanden sind, müssen Sie die Aliase berücksichtigen und die Pfadkarte aktualisieren.

Verlauf

Wir kehren zum Konstruktor von VueRouter zurück. Es gibt drei Modi: Verlauf, Hash und Abstrakt. · Standard-Hash: verwendet URL-Hash-Wert als Route, unterstützt alle Browser

· Verlauf: basiert auf der HTML5-Verlaufs-API und der Serverkonfiguration
· Zusammenfassung: unterstützt alle JavaScript-Laufzeitumgebungen, wie z. B. Node.js serverseitig. Wenn keine Browser-API gefunden wird, wechselt der Router automatisch in diesen Modus.
Der Standardwert ist Hash, und Routen werden durch „#“ getrennt. Wenn jedoch Ankerlinks im Projekt oder Hash-Werte in der Route vorhanden sind, hat das ursprüngliche „#“ Auswirkungen auf Seitensprünge Sie müssen den Verlaufsmodus verwenden.
Was wir üblicherweise in Anwendungen verwenden, ist im Grunde der Verlaufsmodus. Werfen wir einen Blick auf den HashHistory-Konstruktor

var History = function History (router, base) {  this.router = router;  this.base = normalizeBase(base);  this.current = START;  this.pending = null;  this.ready = false;  this.readyCbs = [];  this.readyErrorCbs = [];  this.errorCbs = [];
};
Da Hash und Verlauf einige Ähnlichkeiten aufweisen, wird HashHistory im Verlaufskonstruktor erweitert. Die Bedeutung jedes Attributs lautet wie folgt:

  • this.router stellt die VueRouter-Instanz dar

  • this.base stellt den Basispfad der Anwendung dar. Wenn beispielsweise die gesamte Einzelseitenanwendung unter /app/ bereitgestellt wird, sollte Basis auf

    „/app/“ festgelegt werden. normalizeBase() wird zum Formatieren von base
    verwendet

  • this.current开始时的route,route使用createRoute()创建

  • this.pending表示进行时的route

  • this.ready表示准备状态

  • this.readyCbs表示准备回调函数

creatRoute()在文件src/util/route.js中,下面是他的实现

           
function createRoute (  record,
  location,
  redirectedFrom,
  router
) {
  var stringifyQuery$$1 = router && router.options.stringifyQuery;

  var query = location.query || {};
  try {
    query = clone(query);
  } catch (e) {}

  var route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || &#39;/&#39;,
    hash: location.hash || &#39;&#39;,
    query: query,
    params: location.params || {},
    fullPath: getFullPath(location, stringifyQuery$$1),
    matched: record ? formatMatch(record) : []
  };  if (redirectedFrom) {
    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery$$1);
  }  return Object.freeze(route)
}

createRoute有三个参数,record表示路由记录,location,redirectedFrom表示url地址信息对象,router表示VueRouter实例对象。通过传入的参数,返回一个冻结的route对象,route对象里边包含了一些有关location的属性。History包含了一些基本的方法,例如比较重要的方法有transitionTo(),下面是transitionTo()的具体实现。

           
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {    var this$1 = this;  var route = this.router.match(location, this.current);  this.confirmTransition(route, function () {    this$1.updateRoute(route);
    onComplete && onComplete(route);    this$1.ensureURL();    // fire ready cbs once    if (!this$1.ready) {      this$1.ready = true;      this$1.readyCbs.forEach(function (cb) { cb(route); });
    }
  }, function (err) {    if (onAbort) {
      onAbort(err);
    }    if (err && !this$1.ready) {      this$1.ready = true;      this$1.readyErrorCbs.forEach(function (cb) { cb(err); });
    }
  });
};

首先match得到匹配的route对象,route对象在之前已经提到过。然后使用confirmTransition()确认过渡,更新route,ensureURL()的作用就是更新URL。如果ready为false,更改ready的值,然后对readyCbs数组进行遍历回调。下面来看看HTML5History的构造函数

           
var HTML5History = (function (History$$1) {  function HTML5History (router, base) {    var this$1 = this;

    History$$1.call(this, router, base);    var initLocation = getLocation(this.base);    window.addEventListener(&#39;popstate&#39;, function (e) {      var current = this$1.current;      var location = getLocation(this$1.base);      if (this$1.current === START && location === initLocation) {        return
      }
    });
  }  if ( History$$1 ) HTML5History.__proto__ = History$$1;
  HTML5History.prototype = Object.create( History$$1 && History$$1.prototype );
  HTML5History.prototype.constructor = HTML5History;


  HTML5History.prototype.push = function push (location, onComplete, onAbort) {    var this$1 = this;    var ref = this;    var fromRoute = ref.current;    this.transitionTo(location, function (route) {
      pushState(cleanPath(this$1.base + route.fullPath));
      handleScroll(this$1.router, route, fromRoute, false);
      onComplete && onComplete(route);
    }, onAbort);
  };

  HTML5History.prototype.replace = function replace (location, onComplete, onAbort) {    var this$1 = this;    var ref = this;    var fromRoute = ref.current;    this.transitionTo(location, function (route) {
      replaceState(cleanPath(this$1.base + route.fullPath));
      handleScroll(this$1.router, route, fromRoute, false);
      onComplete && onComplete(route);
    }, onAbort);
  };  return HTML5History;
}(History))

在HTML5History()中代码多次用到了getLocation()那我们来看看他的具体实现吧

           
function getLocation (base) {  var path = window.location.pathname;  if (base && path.indexOf(base) === 0) {
    path = path.slice(base.length);
  }  return (path || &#39;/&#39;) + window.location.search + window.location.hash
}

用一个简单的地址来解释代码中各个部分的含义。例如http://example.com:1234/test/test.htm#part2?a=123,window.location.pathname=>/test/test.htm=>?a=123,window.location.hash=>#part2。
把我们继续回到HTML5History()中,首先继承history构造函数。然后监听popstate事件。当活动记录条目更改时,将触发popstate事件。需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。我们来看看HTML5History的push方法。location表示url信息,onComplete表示成功后的回调函数,onAbort表示失败的回调函数。首先获取current属性值,replaceState和pushState用于更新url,然后处理滚动。模式的选择就大概讲完了,我们回到入口文件,看看init()方法,app代表的是Vue的实例,现将app存入this.apps中,如果this.app已经存在就返回,如果不是就赋值。this.history是三种的实例对象,然后分情况进行transtionTo()操作,history方法就是给history.cb赋值穿进去的回调函数。
下面看getMatchedComponents(),唯一需要注意的就是我们多次提到的route.matched是路由记录的数据,最终返回的是每个路由记录的components属性值的值。

Router-View

最后讲讲router-view

           
var View = {
  name: &#39;router-view&#39;,
  functional: true,
  props: {
    name: {
      type: String,      default: &#39;default&#39;
    }
  },
  render: function render (_, ref) {    var props = ref.props;    var children = ref.children;    var parent = ref.parent;    var data = ref.data;    // 解决嵌套深度问题
    data.routerView = true;    var h = parent.$createElement;    var name = props.name;    // route    var route = parent.$route;    // 缓存    var cache = parent._routerViewCache || (parent._routerViewCache = {});    // 组件的嵌套深度    var depth = 0;    // 用于设置class值    var inactive = false;    // 组件的嵌套深度    while (parent && parent._routerRoot !== parent) {      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++;
      }      if (parent._inactive) {
        inactive = true;
      }      parent = parent.$parent;
    }
    data.routerViewDepth = depth;    if (inactive) {      return h(cache[name], data, children)
    }    var matched = route.matched[depth];    if (!matched) {
      cache[name] = null;      return h()
    }    var component = cache[name] = matched.components[name];

    data.registerRouteInstance = function (vm, val) {      // val could be undefined for unregistration      var current = matched.instances[name];      if (
        (val && current !== vm) ||
        (!val && current === vm)
      ) {
        matched.instances[name] = val;
      }
    }

    ;(data.hook || (data.hook = {})).prepatch = function (_, vnode) {
      matched.instances[name] = vnode.componentInstance;
    };    var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);    if (propsToPass) {
      propsToPass = data.props = extend({}, propsToPass);      var attrs = data.attrs = data.attrs || {};      for (var key in propsToPass) {        if (!component.props || !(key in component.props)) {
          attrs[key] = propsToPass[key];
          delete propsToPass[key];
        }
      }
    }    return h(component, data, children)
  }
};

router-view比较简单,functional为true使组件无状态 (没有 data ) 和无实例 (没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使他们更容易渲染。props表示接受属性,下面来看看render函数,首先获取数据,然后缓存,_inactive用于处理keep-alive情况,获取路由记录,注册Route实例,h()用于渲染。很简单我也懒得一一再说。

相关推荐:

vue-router的权限控制代码分享

Vue-router结合transition实现app动画切换效果实例分享

三种Vue-Router实现组件间跳转的方法

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der Vue-Router-Quellcode-Beispiele. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn