摘要:我們在回到的構造函數中,往下看是模式的選擇,一共這么幾種模式一種和三種。把我們繼續回到中,首先繼承構造函數。表示信息,表示成功后的回調函數,表示失敗的回調函數。是三種的實例對象,然后分情況進行操作,方法就是給賦值穿進去的回調函數。
如同分析vuex源碼我們首先通過一個簡單例子進行了解vue-router是如何使用的,然后在分析在源碼中是如何實現的
示例下面示例來自于example/basica/app.js
import Vue from "vue" import VueRouter from "vue-router" Vue.use(VueRouter) const Home = { template: "home" } const Foo = { template: "foo" } const Bar = { template: "bar" } const router = new VueRouter({ mode: "history", base: __dirname, routes: [ { path: "/", component: Home }, { path: "/foo", component: Foo }, { path: "/bar", component: Bar } ] }) new Vue({ router, template: ` ` }).$mount("#app")
首先調用Vue.use(VueRouter),Vue.use()方法是Vue用來進行插件安裝的方法,這里主要用來安裝VueRouter。然后實例化了VueRouter,我們來看看VueRouter這個構造函數到底做了什么。
從源碼入口文件src/index.js開始看
import type { Matcher } from "./create-matcher" 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 || "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: if (process.env.NODE_ENV !== "production") { 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{ 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] === "*") { 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: "/foo", component: Foo, children: [ // 這也是個 route record { path: "bar", 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 || "/", hash: location.hash || "", 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("popstate", 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 || "/") + 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
var View = { name: "router-view", functional: true, props: { name: { type: String, default: "default" } }, 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()用于渲染。很簡單我也懶得一一再說。
小結文章由入口文件入手,推導出本篇文章。由于篇幅限制,代碼進行了一定的省略,將一些比較簡單的代碼進行了省略。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107292.html
摘要:各位看官沒看過功能梳理的可以先閱讀下源碼學習一功能梳理前車之鑒有了源碼學習的經驗,每次看認真鉆研源代碼的時候都會抽出一小段時間來大體瀏覽一遍源代碼。大體了解這個源代碼的脈絡,每個階段做了什么,文件目錄的劃分。 各位看官 沒看過功能梳理的可以先閱讀下Vuex源碼學習(一)功能梳理. 前車之鑒 有了vue-router源碼學習的經驗,每次看認真鉆研源代碼的時候都會抽出一小段時間來大體瀏覽一...
摘要:而組件在創建時,又怎么會去調用呢這是由于將自身作為一個插件安裝到了,通過注冊了一個鉤子函數,從而在之后所有的組件創建時都會調用該鉤子函數,給了檢查是否有參數,從而進行初始化的機會。 vue-router 是 Vue.js 官方的路由庫,本著學習的目的,我對 vue-router 的源碼進行了閱讀和分析,分享出來給其他感興趣的同學做個參考吧。 參考 源碼:vuejs/vue-route...
摘要:源碼解讀閱讀請關注下代碼注釋打個廣告哪位大佬教我下怎么排版啊,不會弄菜單二級導航撲通是什么首先,你會從源碼里面引入,然后再傳入參數實例化一個路由對象源碼基礎類源碼不選擇模式會默認使用模式非瀏覽器環境默認環境根據參數選擇三種模式的一種根據版 router源碼解讀 閱讀請關注下代碼注釋 打個廣告:哪位大佬教我下sf怎么排版啊,不會弄菜單二級導航(撲通.gif) showImg(https:...
摘要:源碼解讀閱讀請關注下代碼注釋打個廣告哪位大佬教我下怎么排版啊,不會弄菜單二級導航撲通是什么首先,你會從源碼里面引入,然后再傳入參數實例化一個路由對象源碼基礎類源碼不選擇模式會默認使用模式非瀏覽器環境默認環境根據參數選擇三種模式的一種根據版 router源碼解讀 閱讀請關注下代碼注釋 打個廣告:哪位大佬教我下sf怎么排版啊,不會弄菜單二級導航(撲通.gif) showImg(https:...
閱讀 2831·2021-09-28 09:45
閱讀 1507·2021-09-26 10:13
閱讀 897·2021-09-04 16:45
閱讀 3661·2021-08-18 10:21
閱讀 1084·2019-08-29 15:07
閱讀 2633·2019-08-29 14:10
閱讀 3147·2019-08-29 13:02
閱讀 2459·2019-08-29 12:31