前言
本文是vue-router 2.x源碼分析的第二篇侨把,主要看matcher和history的處理過程鸿秆!
實(shí)例代碼
同上節(jié)
1铛漓、matcher
看下createMatcher函數(shù)
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) {
...
}
function redirect (record,location) {
...
}
function alias (record,location,matchAs) {
...
}
function _createRoute (record,location,redirectedFrom) {
...
}
return {
match: match,
addRoutes: addRoutes
}
}
該函數(shù)返回了一個(gè)包含match和addRoutes屬性的對(duì)象愕贡,這里主要看下createRouteMap函數(shù):
function createRouteMap (routes,oldPathList,oldPathMap,oldNameMap) {
//pathList是用來控制path匹配優(yōu)先級(jí)的
var pathList = oldPathList || [];
var pathMap = oldPathMap || Object.create(null);
var nameMap = oldNameMap || Object.create(null);
//循環(huán)調(diào)用addRouteRecord函數(shù)完善pathList, pathMap, nameMap
routes.forEach(function (route) {
addRouteRecord(pathList, pathMap, nameMap, route);
});
// 確保通配符路徑總是在pathList數(shù)組末尾
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
}
}
該函數(shù)將routes轉(zhuǎn)化成這樣的對(duì)象:
ref:{
nameMap:Object //name路由
pathList:Array(3)
pathMap:Object //path路由
__proto__:Object
}
//本實(shí)例中是path路由潦嘶,生成的pathMap如下:
pathMap:{
"":Object
/bar:Object
/foo:Object
}
//其中第一個(gè)Object如下,該對(duì)象即是路由記錄record:
{
beforeEnter:undefined
components:Object
instances:Object
matchAs:undefined
meta:Object
name:undefined
parent:undefined
path:""
props:Object
redirect:undefined
regex:/^(?:\/(?=$))?$/i
__proto__:Object
}
可以看到createRouteMap主要調(diào)用了addRouteRecord函數(shù)伦忠,該函數(shù)如下:
function addRouteRecord (pathList,pathMap,nameMap,route,parent,matchAs) {
var path = route.path;
var name = route.name;
//略過錯(cuò)誤處理部分
...
//修正path
var normalizedPath = normalizePath(path, parent);
//根據(jù)傳入的route構(gòu)造路由記錄record
var record = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath),
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) {
// Warn if route is named and has a default child route.
// If users navigate to this route by name, the default child will
// not be rendered (GH Issue #629)
{
if (route.name && route.children.some(function (child) { return /^\/?$/.test(child.path); })) {
warn(
false,
"Named Route '" + (route.name) + "' has a default child route. " +
"When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
"the default child route will not be rendered. Remove the name from " +
"this route and use the name of the default child route for named " +
"links instead."
);
}
}
route.children.forEach(function (child) {
var childMatchAs = matchAs
? cleanPath((matchAs + "/" + (child.path)))
: undefined;
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
});
}
//處理路由別名
if (route.alias !== undefined) {
//alias是數(shù)組
if (Array.isArray(route.alias)) {
route.alias.forEach(function (alias) {
var aliasRoute = {
path: alias,
children: route.children
};
addRouteRecord(pathList, pathMap, nameMap, aliasRoute, parent, record.path);
});
//alias是字符串
} else {
var aliasRoute = {
path: route.alias,
children: route.children
};
addRouteRecord(pathList, pathMap, nameMap, aliasRoute, parent, record.path);
}
}
//填充pathList,pathMap,nameMap
if (!pathMap[record.path]) {
pathList.push(record.path);
pathMap[record.path] = record;
}
if (name) {
if (!nameMap[name]) {
nameMap[name] = record;
} else if ("development" !== 'production' && !matchAs) {
warn(
false,
"Duplicate named routes definition: " +
"{ name: \"" + name + "\", path: \"" + (record.path) + "\" }"
);
}
}
}
2省核、History
- 先看HashHistory
function HashHistory (router, base, fallback) {
History.call(this, router, base);
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash();
}
History長(zhǎng)這樣
var History = function History (router, base) {
this.router = router;
//base最佳寫法:'/base',以斜杠開頭昆码,不以斜杠結(jié)尾
this.base = normalizeBase(base);
// start with a route object that stands for "nowhere"
this.current = START;
this.pending = null;
this.ready = false;
this.readyCbs = [];
this.readyErrorCbs = [];
this.errorCbs = [];
};
History.prototype上有這些方法
History.prototype={
listen:function(){...},
onReady:function(){...},
onError:function(){...},
transitionTo:function(){...},
confirmTransition:function(){...},
updateRoute:function(){...}
}
//以下7.10新增
還記得上篇中router的初始化時(shí)關(guān)于history的處理嗎气忠,
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation());
} else if (history instanceof HashHistory) {
var setupHashListener = function () {
history.setupListeners();
};
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
);
}
//監(jiān)聽route,一旦route發(fā)生改變就賦值給app._route從而觸發(fā)頁(yè)面
//更新,達(dá)到特定route繪制特定組件的目的
history.listen(function (route) {
this$1.apps.forEach(function (app) {
app._route = route;
});
});
我們以hash模式為主來分析赋咽,可以看到執(zhí)行了history.transitionTo方法旧噪,該方法接受了三個(gè)參數(shù)history.getCurrentLocation(),setupHashListener和setupHashListener。
先看getCurrentLocation方法脓匿,返回當(dāng)前hash值
HashHistory.prototype.getCurrentLocation = function getCurrentLocation () {
return getHash()
};
//getHash函數(shù)如下:
function getHash () {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
var href = window.location.href;
var index = href.indexOf('#');
return index === -1 ? '' : href.slice(index + 1)
}
再看transitionTo方法
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
var this$1 = this;
//調(diào)用match方法取得匹配到的route
var route = this.router.match(location, this.current);
//調(diào)用confirmTransition方法
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方法
VueRouter.prototype.match = function match (raw,current,redirectedFrom) {
return this.matcher.match(raw, current, redirectedFrom)
};
//this.matcher.match如下,該函數(shù)經(jīng)過層層調(diào)用最終返回了一個(gè)route對(duì)象淘钟,注意跟路由記錄record對(duì)象的區(qū)別
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"));
}
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];
}
}
}
if (record) {
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)
}
未完待續(xù)。陪毡。