前言
本文是vue-router 2.x源码分析的第二篇,主要看matcher和history的处理过程!
实例代码
同上节
1、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) {...}function redirect (record,location) {...}function alias (record,location,matchAs) {...}function _createRoute (record,location,redirectedFrom) {...}return {match: match,addRoutes: addRoutes}}
该函数返回了一个包含match和addRoutes属性的对象,这里主要看下createRouteMap函数:
function createRouteMap (routes,oldPathList,oldPathMap,oldNameMap) {//pathList是用来控制path匹配优先级的var pathList = oldPathList || [];var pathMap = oldPathMap || Object.create(null);var nameMap = oldNameMap || Object.create(null);//循环调用addRouteRecord函数完善pathList, pathMap, nameMaproutes.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}}
该函数将routes转化成这样的对象:
ref:{nameMap:Object //name路由pathList:Array(3)pathMap:Object //path路由__proto__:Object}//本实例中是path路由,生成的pathMap如下:pathMap:{"":Object/bar:Object/foo:Object}//其中第一个Object如下,该对象即是路由记录record:{beforeEnter:undefinedcomponents:Objectinstances:ObjectmatchAs:undefinedmeta:Objectname:undefinedparent:undefinedpath:""props:Objectredirect:undefinedregex:/^(?:\/(?=$))?$/i__proto__:Object}
可以看到createRouteMap主要调用了addRouteRecord函数,该函数如下:
function addRouteRecord (pathList,pathMap,nameMap,route,parent,matchAs) {var path = route.path;var name = route.name;//略过错误处理部分...//修正pathvar normalizedPath = normalizePath(path, parent);//根据传入的route构造路由记录recordvar 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是数组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,nameMapif (!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 deeplinkingif (fallback && checkFallback(this.base)) {return}ensureSlash();}
History长这样
var History = function History (router, base) {this.router = router;//base最佳写法:'/base',以斜杠开头,不以斜杠结尾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的初始化时关于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);}//监听route,一旦route发生改变就赋值给app._route从而触发页面//更新,达到特定route绘制特定组件的目的history.listen(function (route) {this$1.apps.forEach(function (app) {app._route = route;});});
我们以hash模式为主来分析,可以看到执行了history.transitionTo方法,该方法接受了三个参数history.getCurrentLocation(),setupHashListener和setupHashListener。
先看getCurrentLocation方法,返回当前hash值
HashHistory.prototype.getCurrentLocation = function getCurrentLocation () {return getHash()};//getHash函数如下: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;//调用match方法取得匹配到的routevar route = this.router.match(location, this.current);//调用confirmTransition方法this.confirmTransition(route, function () {this$1.updateRoute(route);onComplete && onComplete(route);this$1.ensureURL();// fire ready cbs onceif (!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如下,该函数经过层层调用最终返回了一个route对象,注意跟路由记录record对象的区别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 matchreturn _createRoute(null, location)}
未完待续。。
