摘要:我們在回到的構(gòu)造函數(shù)中,往下看是模式的選擇,一共這么幾種模式一種和三種。把我們繼續(xù)回到中,首先繼承構(gòu)造函數(shù)。表示信息,表示成功后的回調(diào)函數(shù),表示失敗的回調(diào)函數(shù)。是三種的實例對象,然后分情況進行操作,方法就是給賦值穿進去的回調(diào)函數(shù)。
如同分析vuex源碼我們首先通過一個簡單例子進行了解vue-router是如何使用的,然后在分析在源碼中是如何實現(xiàn)的
示例下面示例來自于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")
首先調(diào)用Vue.use(VueRouter),Vue.use()方法是Vue用來進行插件安裝的方法,這里主要用來安裝VueRouter。然后實例化了VueRouter,我們來看看VueRouter這個構(gòu)造函數(shù)到底做了什么。
從源碼入口文件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函數(shù)的實現(xiàn),首先進行初始化我們來看看這些初始化條件分別代表的是什么
this.app表示當(dāng)前Vue實例
this.apps表示所有app組件
this.options表示傳入的VueRouter的選項
this.resolveHooks表示resolve鉤子回調(diào)函數(shù)的數(shù)組,resolve用于解析目標(biāo)位置
this.matcher創(chuàng)建匹配函數(shù)
代碼中有createMatcher()函數(shù),來看看他的實現(xiàn)
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用于創(chuàng)建路由
return _createRoute(record, location, redirectedFrom)
} else if (location.path) {
// 普通路由處理
}
// no match
return _createRoute(null, location)
}
return {
match: match,
addRoutes: addRoutes
}
}
createMatcher()有兩個參數(shù)routes表示創(chuàng)建VueRouter傳入的routes配置信息,router表示VueRouter實例。createMatcher()的作用就是傳入的routes通過createRouteMap創(chuàng)建對應(yīng)的map,和一個創(chuàng)建map的方法。
我們先來看看createRouteMap()方法的定義
function createRouteMap (
routes,
oldPathList,
oldPathMap,
oldNameMap
) {
// 用于控制匹配優(yōu)先級
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個參數(shù):routes代表的配置信息,oldPathList包含所有路徑的數(shù)組用于匹配優(yōu)先級,oldNameMap表示name map,oldPathMap表示path map。createRouteMap就是更新pathList,nameMap和pathMap。nameMap到底代表的是什么呢?它是包含路由記錄的一個對象,每個屬性值名是每個記錄的path屬性值,屬性值就是具有這個path屬性值的路由記錄。這兒有一個叫路由記錄的東西,這是什么意思呢?路由記錄就是 routes 配置數(shù)組中的對象副本(還有在 children 數(shù)組),路由記錄都是包含在matched屬性中例如
const router = new VueRouter({
routes: [
// 下面的對象就是 route record
{ path: "/foo", component: Foo,
children: [
// 這也是個 route record
{ path: "bar", component: Bar }
]
}
]
})
在上面代碼中用一段代碼用于給每個route添加路由記錄,那么路由記錄的實現(xiàn)是如何的呢,下面是addRouteReord()的實現(xiàn)
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()這個函數(shù)的參數(shù)我都懶得說什么意思了,新增的parent也表示路由記錄,首先獲取path,name。然后通過normalizePath()規(guī)范格式,然后就是record這個對象的建立,然后遍歷routes的子元素添加路由記錄如果有別名的情況還需要考慮別名的情況然后更新path Map。
History我們在回到VueRouter的構(gòu)造函數(shù)中,往下看是模式的選擇,一共這么幾種模式一種history,hash和abstract三種?!?默認(rèn)hash: 使用URL hash值作為路由,支持所有瀏覽器
· history: 依賴HTML5 History API和服務(wù)器配置
· abstract:支持所有 JavaScript 運行環(huán)境,如 Node.js 服務(wù)器端。如果發(fā)現(xiàn)沒有瀏覽器的 API,路由會自動強制進入這個模式。
默認(rèn)是hash,路由通過“#”隔開,但是如果工程中有錨鏈接或者路由中有hash值,原先的“#”就會對頁面跳轉(zhuǎn)產(chǎn)生影響;所以就需要使用history模式。
在應(yīng)用中我們常用的基本都是history模式,下面我們來看看HashHistory的構(gòu)造函數(shù)
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構(gòu)造函數(shù)上進行擴展下面是各個屬性所代表的意義:
this.router表示VueRouter實例
this.base表示應(yīng)用的基路徑。例如,如果整個單頁應(yīng)用服務(wù)在 /app/ 下,然后 base 就應(yīng)該設(shè)為
"/app/"。normalizeBase()用于格式化base
this.current開始時的route,route使用createRoute()創(chuàng)建
this.pending表示進行時的route
this.ready表示準(zhǔn)備狀態(tài)
this.readyCbs表示準(zhǔn)備回調(diào)函數(shù)
creatRoute()在文件src/util/route.js中,下面是他的實現(xiàn)
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有三個參數(shù),record表示路由記錄,location,redirectedFrom表示url地址信息對象,router表示VueRouter實例對象。通過傳入的參數(shù),返回一個凍結(jié)的route對象,route對象里邊包含了一些有關(guān)location的屬性。History包含了一些基本的方法,例如比較重要的方法有transitionTo(),下面是transitionTo()的具體實現(xiàn)。
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對象在之前已經(jīng)提到過。然后使用confirmTransition()確認(rèn)過渡,更新route,ensureURL()的作用就是更新URL。如果ready為false,更改ready的值,然后對readyCbs數(shù)組進行遍歷回調(diào)。下面來看看HTML5History的構(gòu)造函數(shù)
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()那我們來看看他的具體實現(xiàn)吧
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。
把我們繼續(xù)回到HTML5History()中,首先繼承history構(gòu)造函數(shù)。然后監(jiān)聽popstate事件。當(dāng)活動記錄條目更改時,將觸發(fā)popstate事件。需要注意的是調(diào)用history.pushState()或history.replaceState()不會觸發(fā)popstate事件。我們來看看HTML5History的push方法。location表示url信息,onComplete表示成功后的回調(diào)函數(shù),onAbort表示失敗的回調(diào)函數(shù)。首先獲取current屬性值,replaceState和pushState用于更新url,然后處理滾動。模式的選擇就大概講完了,我們回到入口文件,看看init()方法,app代表的是Vue的實例,現(xiàn)將app存入this.apps中,如果this.app已經(jīng)存在就返回,如果不是就賦值。this.history是三種的實例對象,然后分情況進行transtionTo()操作,history方法就是給history.cb賦值穿進去的回調(diào)函數(shù)。
下面看getMatchedComponents(),唯一需要注意的就是我們多次提到的route.matched是路由記錄的數(shù)據(jù),最終返回的是每個路由記錄的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;
// 用于設(shè)置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使組件無狀態(tài) (沒有 data ) 和無實例 (沒有 this 上下文)。他們用一個簡單的 render 函數(shù)返回虛擬節(jié)點使他們更容易渲染。props表示接受屬性,下面來看看render函數(shù),首先獲取數(shù)據(jù),然后緩存,_inactive用于處理keep-alive情況,獲取路由記錄,注冊Route實例,h()用于渲染。很簡單我也懶得一一再說。
小結(jié)文章由入口文件入手,推導(dǎo)出本篇文章。由于篇幅限制,代碼進行了一定的省略,將一些比較簡單的代碼進行了省略。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.hztianpu.com/yun/107292.html
摘要:各位看官沒看過功能梳理的可以先閱讀下源碼學(xué)習(xí)一功能梳理前車之鑒有了源碼學(xué)習(xí)的經(jīng)驗,每次看認(rèn)真鉆研源代碼的時候都會抽出一小段時間來大體瀏覽一遍源代碼。大體了解這個源代碼的脈絡(luò),每個階段做了什么,文件目錄的劃分。 各位看官 沒看過功能梳理的可以先閱讀下Vuex源碼學(xué)習(xí)(一)功能梳理. 前車之鑒 有了vue-router源碼學(xué)習(xí)的經(jīng)驗,每次看認(rèn)真鉆研源代碼的時候都會抽出一小段時間來大體瀏覽一...
摘要:而組件在創(chuàng)建時,又怎么會去調(diào)用呢這是由于將自身作為一個插件安裝到了,通過注冊了一個鉤子函數(shù),從而在之后所有的組件創(chuàng)建時都會調(diào)用該鉤子函數(shù),給了檢查是否有參數(shù),從而進行初始化的機會。 vue-router 是 Vue.js 官方的路由庫,本著學(xué)習(xí)的目的,我對 vue-router 的源碼進行了閱讀和分析,分享出來給其他感興趣的同學(xué)做個參考吧。 參考 源碼:vuejs/vue-route...
摘要:源碼解讀閱讀請關(guān)注下代碼注釋打個廣告哪位大佬教我下怎么排版啊,不會弄菜單二級導(dǎo)航撲通是什么首先,你會從源碼里面引入,然后再傳入?yún)?shù)實例化一個路由對象源碼基礎(chǔ)類源碼不選擇模式會默認(rèn)使用模式非瀏覽器環(huán)境默認(rèn)環(huán)境根據(jù)參數(shù)選擇三種模式的一種根據(jù)版 router源碼解讀 閱讀請關(guān)注下代碼注釋 打個廣告:哪位大佬教我下sf怎么排版啊,不會弄菜單二級導(dǎo)航(撲通.gif) showImg(https:...
摘要:源碼解讀閱讀請關(guān)注下代碼注釋打個廣告哪位大佬教我下怎么排版啊,不會弄菜單二級導(dǎo)航撲通是什么首先,你會從源碼里面引入,然后再傳入?yún)?shù)實例化一個路由對象源碼基礎(chǔ)類源碼不選擇模式會默認(rèn)使用模式非瀏覽器環(huán)境默認(rèn)環(huán)境根據(jù)參數(shù)選擇三種模式的一種根據(jù)版 router源碼解讀 閱讀請關(guān)注下代碼注釋 打個廣告:哪位大佬教我下sf怎么排版啊,不會弄菜單二級導(dǎo)航(撲通.gif) showImg(https:...
閱讀 2923·2021-09-28 09:45
閱讀 1583·2021-09-26 10:13
閱讀 998·2021-09-04 16:45
閱讀 3794·2021-08-18 10:21
閱讀 1184·2019-08-29 15:07
閱讀 2713·2019-08-29 14:10
閱讀 3233·2019-08-29 13:02
閱讀 2548·2019-08-29 12:31