目錄
範例
History
Router-View
首頁 web前端 js教程 vue-router原始碼實例詳解

vue-router原始碼實例詳解

Feb 07, 2018 pm 02:47 PM
vue-router 原始碼 詳解

本文主要和大家分享vue-router源碼閱讀學習,如同分析vuex源碼我們首先透過一個簡單例子進行了解vue-router是如何使用的,然後在分析在源碼中是如何實現的,希望能幫助大家。

範例

下列範例來自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;)
登入後複製

首先呼叫Vue.use(VueRouter),Vue.use()方法是Vue用來進行插件安裝的方法,這裡主要用來安裝VueRouter。然後實例化了VueRouter,讓我們來看看VueRouter這個建構子到底做了什麼。
從原始碼入口檔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]
      })
    }))
  }

}
登入後複製

程式碼一步一步地看,先從constructor函數的實作,首先進行初始化我們來看看這些初始化條件分別代表的是什麼

  • this.app表示當前Vue實例

  • this.apps表示所有app元件

  • this.options表示傳入的VueRouter的選項

  • this.resolveHooks表示resolve鉤子回呼函數的數組,resolve用於解析目標位置

  • this.matcher建立匹配函數

#程式碼中有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 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()有兩個參數routes表示建立VueRouter傳入的routes設定資訊,router表示VueRouter實例。 createMatcher()的功能是傳入的routes透過createRouteMap建立對應的map,和一個建立map的方法。
我們先來看看createRouteMap()方法的定義

           
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()有4個參數:routes代表的配置訊息,oldPathList包含所有路徑的陣列用於匹配優先權,oldNameMap表示name map,oldPathMap表示path map。 createRouteMap就是更新pathList,nameMap和pathMap。 nameMap到底代表的是什麼呢?它是包含路由記錄的一個對象,每個屬性值名是每個記錄的path屬性值,屬性值就是具有這個path屬性值的路由記錄。這裡有一個叫路由記錄的東西,這是什麼意思呢?路由記錄就是routes 設定陣列中的物件副本(還有在children 陣列),路由記錄都是包含在matched屬性中例如

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

在上面程式碼中用一段程式碼用來為每個route新增路由記錄,那麼路由記錄的實作是如何的呢,以下是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;
  }
}
登入後複製

addRouteRecord()這個函數的參數我都懶得說什麼意思了,新增的parent也表示路由記錄,首先取得path,name。然後透過normalizePath()規範格式,然後就是record這個物件的建立,然後遍歷routes的子元素加入路由記錄如果有別名的情況還需要考慮別名的情況然後更新path Map。

History

我們在回到VueRouter的建構子中,往下看是模式的選擇,一共這麼多種模式一種history,hash和abstract三種。 · 預設hash: 使用URL hash值作為路由,支援所有瀏覽器
· history: 依賴HTML5 History API和伺服器設定
· abstract:支援所有 JavaScript 運行環境,如 Node.js 伺服器端。如果發現沒有瀏覽器的 API,路由會自動強制進入這個模式。
預設是hash,路由透過「#」隔開,但是如果工程中有錨點連結或是路由中有hash值,原先的「#」就會對頁面跳躍產生影響;所以就需要使用history模式。
在應用程式中我們常用的基本上都是history模式,我們來看看HashHistory的建構子

           
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 = [];
};
登入後複製

因為hash和history有一些相同的地方,所以HashHistory會在History建構子上進行擴充下面是各個屬性所代表的意義:

  • this.router表示VueRouter實例

  • this.base表示應用的基底路徑。例如,如果整個單頁應用程式服務在 /app/ 下,然後 base 就應該設為
     "/app/"。 normalizeBase()用於格式化base

  • 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实现组件间跳转的方法

以上是vue-router原始碼實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Win11管理員權限取得詳解 Win11管理員權限取得詳解 Mar 08, 2024 pm 03:06 PM

Windows作業系統是全球最受歡迎的作業系統之一,其新版本Win11備受矚目。在Win11系統中,管理員權限的取得是一個重要的操作,管理員權限可以讓使用者對系統進行更多的操作和設定。本文將詳細介紹在Win11系統中如何取得管理員權限,以及如何有效地管理權限。在Win11系統中,管理員權限分為本機管理員和網域管理員兩種。本機管理員是指具有對本機電腦的完全管理權限

Oracle SQL中的除法運算詳解 Oracle SQL中的除法運算詳解 Mar 10, 2024 am 09:51 AM

OracleSQL中的除法運算詳解在OracleSQL中,除法運算是一種常見且重要的數學運算運算,用來計算兩個數相除的結果。除法在資料庫查詢中經常用到,因此了解OracleSQL中的除法運算及其用法是資料庫開發人員必備的技能之一。本文將詳細討論OracleSQL中除法運算的相關知識,並提供具體的程式碼範例供讀者參考。一、OracleSQL中的除法運算

PHP模運算子的作用及用法詳解 PHP模運算子的作用及用法詳解 Mar 19, 2024 pm 04:33 PM

PHP中的模運算子(%)是用來取得兩個數值相除的餘數的。在本文中,我們將詳細討論模運算子的作用及用法,並提供具體的程式碼範例來幫助讀者更好地理解。 1.模運算子的作用在數學中,當我們將一個整數除以另一個整數時,就會得到一個商和一個餘數。例如,當我們將10除以3時,商數為3,餘數為1。模運算子就是用來取得這個餘數的。 2.模運算子的用法在PHP中,使用%符號來表示模

linux系統呼叫system()函數詳解 linux系統呼叫system()函數詳解 Feb 22, 2024 pm 08:21 PM

Linux系統呼叫system()函數詳解系統呼叫是Linux作業系統中非常重要的一部分,它提供了一種與系統核心互動的方式。其中,system()函數是常用的系統呼叫函數之一。本文將詳細介紹system()函數的使用方法,並提供對應的程式碼範例。系統呼叫的基本概念系統呼叫是使用者程式與作業系統核心互動的一種方式。使用者程式透過呼叫系統呼叫函數來請求作業系統

Linux的curl指令詳解 Linux的curl指令詳解 Feb 21, 2024 pm 10:33 PM

Linux的curl命令詳解摘要:curl是一種強大的命令列工具,用於與伺服器進行資料通訊。本文將介紹curl指令的基本用法,並提供實際的程式碼範例,幫助讀者更好地理解和應用該指令。一、curl是什麼? curl是命令列工具,用於發送和接收各種網路請求。它支援多種協議,如HTTP、FTP、TELNET等,並提供了豐富的功能,如檔案上傳、檔案下載、資料傳輸、代

PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? Mar 11, 2024 am 10:54 AM

PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? PHP是一種伺服器端腳本語言,通常用於開發動態網頁。當PHP檔案在伺服器上被要求時,伺服器會解釋執行其中的PHP程式碼,並將最終的HTML內容傳送到瀏覽器以供顯示。然而,有時我們希望在瀏覽器中直接展示PHP檔案的原始碼,而不是被執行。本文將介紹如何在瀏覽器中顯示PHP程式碼的源碼,而不被解釋執行。在PHP中,可以使

詳細解析C語言學習路線 詳細解析C語言學習路線 Feb 18, 2024 am 10:38 AM

C語言作為一門廣泛應用在軟體開發領域的程式語言,是許多程式設計師初學者的首選。學習C語言不僅可以幫助我們建立程式設計的基礎知識,還可以提升我們解決問題和思考的能力。本文將詳細介紹一條C語言學習的路線圖,幫助初學者更好地規劃自己的學習進度。 1.學習基本文法在開始學習C語言之前,我們首先需要先了解C語言的基本文法規則。這包括變數和資料類型、運算子、控制語句(如if語句、

深入了解Promise.resolve() 深入了解Promise.resolve() Feb 18, 2024 pm 07:13 PM

Promise.resolve()詳解,需要具體程式碼範例Promise是JavaScript中一種用來處理非同步操作的機制。在實際開發中,常常需要處理一些需要依序執行的非同步任務,而Promise.resolve()方法就是用來傳回一個已經Fulfilled狀態的Promise物件。 Promise.resolve()是Promise類別的靜態方法,它接受一個

See all articles