前言

本文是vue-router 2.x源码分析的第二篇,主要看matcher和history的处理过程!

实例代码

同上节

1、matcher

看下createMatcher函数

  1. function createMatcher (routes,router) {
  2. var ref = createRouteMap(routes);
  3. var pathList = ref.pathList;
  4. var pathMap = ref.pathMap;
  5. var nameMap = ref.nameMap;
  6. function addRoutes (routes) {
  7. createRouteMap(routes, pathList, pathMap, nameMap);
  8. }
  9. function match (raw,currentRoute,redirectedFrom) {
  10. ...
  11. }
  12. function redirect (record,location) {
  13. ...
  14. }
  15. function alias (record,location,matchAs) {
  16. ...
  17. }
  18. function _createRoute (record,location,redirectedFrom) {
  19. ...
  20. }
  21. return {
  22. match: match,
  23. addRoutes: addRoutes
  24. }
  25. }

该函数返回了一个包含match和addRoutes属性的对象,这里主要看下createRouteMap函数:

  1. function createRouteMap (routes,oldPathList,oldPathMap,oldNameMap) {
  2. //pathList是用来控制path匹配优先级的
  3. var pathList = oldPathList || [];
  4. var pathMap = oldPathMap || Object.create(null);
  5. var nameMap = oldNameMap || Object.create(null);
  6. //循环调用addRouteRecord函数完善pathList, pathMap, nameMap
  7. routes.forEach(function (route) {
  8. addRouteRecord(pathList, pathMap, nameMap, route);
  9. });
  10. // 确保通配符路径总是在pathList数组末尾
  11. for (var i = 0, l = pathList.length; i < l; i++) {
  12. if (pathList[i] === '*') {
  13. pathList.push(pathList.splice(i, 1)[0]);
  14. l--;
  15. i--;
  16. }
  17. }
  18. return {
  19. pathList: pathList,
  20. pathMap: pathMap,
  21. nameMap: nameMap
  22. }
  23. }

该函数将routes转化成这样的对象:

  1. ref:{
  2. nameMap:Object //name路由
  3. pathList:Array(3)
  4. pathMap:Object //path路由
  5. __proto__:Object
  6. }
  7. //本实例中是path路由,生成的pathMap如下:
  8. pathMap:{
  9. "":Object
  10. /bar:Object
  11. /foo:Object
  12. }
  13. //其中第一个Object如下,该对象即是路由记录record:
  14. {
  15. beforeEnter:undefined
  16. components:Object
  17. instances:Object
  18. matchAs:undefined
  19. meta:Object
  20. name:undefined
  21. parent:undefined
  22. path:""
  23. props:Object
  24. redirect:undefined
  25. regex:/^(?:\/(?=$))?$/i
  26. __proto__:Object
  27. }

可以看到createRouteMap主要调用了addRouteRecord函数,该函数如下:

  1. function addRouteRecord (pathList,pathMap,nameMap,route,parent,matchAs) {
  2. var path = route.path;
  3. var name = route.name;
  4. //略过错误处理部分
  5. ...
  6. //修正path
  7. var normalizedPath = normalizePath(path, parent);
  8. //根据传入的route构造路由记录record
  9. var record = {
  10. path: normalizedPath,
  11. regex: compileRouteRegex(normalizedPath),
  12. components: route.components || { default: route.component },
  13. instances: {},
  14. name: name,
  15. parent: parent,
  16. matchAs: matchAs,
  17. redirect: route.redirect,
  18. beforeEnter: route.beforeEnter,
  19. meta: route.meta || {},
  20. props: route.props == null
  21. ? {}
  22. : route.components
  23. ? route.props
  24. : { default: route.props }
  25. };
  26. //处理嵌套路由
  27. if (route.children) {
  28. // Warn if route is named and has a default child route.
  29. // If users navigate to this route by name, the default child will
  30. // not be rendered (GH Issue #629)
  31. {
  32. if (route.name && route.children.some(function (child) { return /^\/?$/.test(child.path); })) {
  33. warn(
  34. false,
  35. "Named Route '" + (route.name) + "' has a default child route. " +
  36. "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
  37. "the default child route will not be rendered. Remove the name from " +
  38. "this route and use the name of the default child route for named " +
  39. "links instead."
  40. );
  41. }
  42. }
  43. route.children.forEach(function (child) {
  44. var childMatchAs = matchAs
  45. ? cleanPath((matchAs + "/" + (child.path)))
  46. : undefined;
  47. addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
  48. });
  49. }
  50. //处理路由别名
  51. if (route.alias !== undefined) {
  52. //alias是数组
  53. if (Array.isArray(route.alias)) {
  54. route.alias.forEach(function (alias) {
  55. var aliasRoute = {
  56. path: alias,
  57. children: route.children
  58. };
  59. addRouteRecord(pathList, pathMap, nameMap, aliasRoute, parent, record.path);
  60. });
  61. //alias是字符串
  62. } else {
  63. var aliasRoute = {
  64. path: route.alias,
  65. children: route.children
  66. };
  67. addRouteRecord(pathList, pathMap, nameMap, aliasRoute, parent, record.path);
  68. }
  69. }
  70. //填充pathList,pathMap,nameMap
  71. if (!pathMap[record.path]) {
  72. pathList.push(record.path);
  73. pathMap[record.path] = record;
  74. }
  75. if (name) {
  76. if (!nameMap[name]) {
  77. nameMap[name] = record;
  78. } else if ("development" !== 'production' && !matchAs) {
  79. warn(
  80. false,
  81. "Duplicate named routes definition: " +
  82. "{ name: \"" + name + "\", path: \"" + (record.path) + "\" }"
  83. );
  84. }
  85. }
  86. }

2、History

  • 先看HashHistory
  1. function HashHistory (router, base, fallback) {
  2. History.call(this, router, base);
  3. // check history fallback deeplinking
  4. if (fallback && checkFallback(this.base)) {
  5. return
  6. }
  7. ensureSlash();
  8. }

History长这样

  1. var History = function History (router, base) {
  2. this.router = router;
  3. //base最佳写法:'/base',以斜杠开头,不以斜杠结尾
  4. this.base = normalizeBase(base);
  5. // start with a route object that stands for "nowhere"
  6. this.current = START;
  7. this.pending = null;
  8. this.ready = false;
  9. this.readyCbs = [];
  10. this.readyErrorCbs = [];
  11. this.errorCbs = [];
  12. };

History.prototype上有这些方法

  1. History.prototype={
  2. listen:function(){...},
  3. onReady:function(){...},
  4. onError:function(){...},
  5. transitionTo:function(){...},
  6. confirmTransition:function(){...},
  7. updateRoute:function(){...}
  8. }

//以下7.10新增
还记得上篇中router的初始化时关于history的处理吗,

  1. if (history instanceof HTML5History) {
  2. history.transitionTo(history.getCurrentLocation());
  3. } else if (history instanceof HashHistory) {
  4. var setupHashListener = function () {
  5. history.setupListeners();
  6. };
  7. history.transitionTo(
  8. history.getCurrentLocation(),
  9. setupHashListener,
  10. setupHashListener
  11. );
  12. }
  13. //监听route,一旦route发生改变就赋值给app._route从而触发页面
  14. //更新,达到特定route绘制特定组件的目的
  15. history.listen(function (route) {
  16. this$1.apps.forEach(function (app) {
  17. app._route = route;
  18. });
  19. });

我们以hash模式为主来分析,可以看到执行了history.transitionTo方法,该方法接受了三个参数history.getCurrentLocation(),setupHashListener和setupHashListener。
先看getCurrentLocation方法,返回当前hash值

  1. HashHistory.prototype.getCurrentLocation = function getCurrentLocation () {
  2. return getHash()
  3. };
  4. //getHash函数如下:
  5. function getHash () {
  6. // We can't use window.location.hash here because it's not
  7. // consistent across browsers - Firefox will pre-decode it!
  8. var href = window.location.href;
  9. var index = href.indexOf('#');
  10. return index === -1 ? '' : href.slice(index + 1)
  11. }

再看transitionTo方法

  1. History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
  2. var this$1 = this;
  3. //调用match方法取得匹配到的route
  4. var route = this.router.match(location, this.current);
  5. //调用confirmTransition方法
  6. this.confirmTransition(route, function () {
  7. this$1.updateRoute(route);
  8. onComplete && onComplete(route);
  9. this$1.ensureURL();
  10. // fire ready cbs once
  11. if (!this$1.ready) {
  12. this$1.ready = true;
  13. this$1.readyCbs.forEach(function (cb) { cb(route); });
  14. }
  15. }, function (err) {
  16. if (onAbort) {
  17. onAbort(err);
  18. }
  19. if (err && !this$1.ready) {
  20. this$1.ready = true;
  21. this$1.readyErrorCbs.forEach(function (cb) { cb(err); });
  22. }
  23. });
  24. };

看看match方法

  1. VueRouter.prototype.match = function match (raw,current,redirectedFrom) {
  2. return this.matcher.match(raw, current, redirectedFrom)
  3. };
  4. //this.matcher.match如下,该函数经过层层调用最终返回了一个route对象,注意跟路由记录record对象的区别
  5. function match (raw, currentRoute,redirectedFrom) {
  6. var location = normalizeLocation(raw, currentRoute, false, router);
  7. var name = location.name;
  8. if (name) {
  9. var record = nameMap[name];
  10. {
  11. warn(record, ("Route with name '" + name + "' does not exist"));
  12. }
  13. var paramNames = record.regex.keys
  14. .filter(function (key) { return !key.optional; })
  15. .map(function (key) { return key.name; });
  16. if (typeof location.params !== 'object') {
  17. location.params = {};
  18. }
  19. if (currentRoute && typeof currentRoute.params === 'object') {
  20. for (var key in currentRoute.params) {
  21. if (!(key in location.params) && paramNames.indexOf(key) > -1) {
  22. location.params[key] = currentRoute.params[key];
  23. }
  24. }
  25. }
  26. if (record) {
  27. location.path = fillParams(record.path, location.params, ("named route \"" + name + "\""));
  28. return _createRoute(record, location, redirectedFrom)
  29. }
  30. } else if (location.path) {
  31. location.params = {};
  32. for (var i = 0; i < pathList.length; i++) {
  33. var path = pathList[i];
  34. var record$1 = pathMap[path];
  35. if (matchRoute(record$1.regex, location.path, location.params)) {
  36. return _createRoute(record$1, location, redirectedFrom)
  37. }
  38. }
  39. }
  40. // no match
  41. return _createRoute(null, location)
  42. }

未完待续。。