vue-router源码分析(实例化)

VueRouter 构造函数

创建一个路由匹配对象,根据mode来采取不同的路由方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
constructor(options: RouterOptions = {}) {
this.app = null;
this.apps = [];
this.options = options;
this.beforeHooks = [];
this.resolveHooks = [];
this.afterHooks = [];
// 创建路由匹配对象
this.matcher = createMatcher(options.routes || [], this);

// 根据 mode 采取不同的路由方式
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}`);
}
}
}

1. createMatcher 创建匹配函数

匹配函数是由 src/create-matcher.js 中的 createMatcher 创建的:

根据传入的 routes 配置生成对应的路由 map,然后直接返回了 match 匹配函数和添加路由的函数addRoutes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* @flow */

// ...
import { createRouteMap } from "./create-route-map";
// ...

export function createMatcher(
routes: Array<RouteConfig>,
router: VueRouter
): Matcher {
// 创建路由映射表
const { pathList, pathMap, nameMap } = createRouteMap(routes);

function addRoutes(routes) {
// ...
}

// 路由匹配
function match(raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location): Route {
// ...
}

function redirect(record: RouteRecord, location: Location): Route {
// ...
}

function alias(record: RouteRecord, location: Location, matchAs: string): Route {
// ...
}

// 根据条件创建不同的路由
function _createRoute(record: ?RouteRecord, location: Location, redirectedFrom?: Location): Route {
// ...
}

return {
match, addRoutes
};
}

2. createRouteMap 创建路由映射函数

src/create-route-map.js

根据传入的 routes 配置生成对应的路由 map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
export function createRouteMap(
routes: Array<RouteConfig>,
oldPathList?: Array<string>,
oldPathMap?: Dictionary<RouteRecord>,
oldNameMap?: Dictionary<RouteRecord>
): {
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>
} {
// 创建映射表
const pathList: Array<string> = oldPathList || [];
// path 路由 map
const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null);
// name 路由 map
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null);

// 遍历路由配置,为每个配置添加路由记录
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route);
});

// 确保通配符在最后
for (let i = 0, l = pathList.length; i < l; i++) {
if (pathList[i] === "*") {
pathList.push(pathList.splice(i, 1)[0]);
l--;
i--;
}
}

if (process.env.NODE_ENV === "development") {
// warn if routes do not include leading slashes
const found = pathList
// check for missing leading slash
.filter(path => path && path.charAt(0) !== "*" && path.charAt(0) !== "/");

if (found.length > 0) {
const pathNames = found.map(path => `- ${path}`).join("\n");
warn(
false,
`Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`
);
}
}

return {
pathList,
pathMap,
nameMap
};
}

addRouteRecord添加路由记录函数

routes里的每个route添加路由记录;

如果routechildren,给每个child添加路由记录;

如果路由有别名的话,给别名也添加路由记录;

建立pathMap,nameMap映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
function addRouteRecord(
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>,
route: RouteConfig,
parent?: RouteRecord,
matchAs?: string
) {
// 获得路由配置下的属性
const { path, name } = route;
if (process.env.NODE_ENV !== "production") {
assert(path != null, `"path" is required in a route configuration.`);
assert(
typeof route.component !== "string",
`route config "component" for path: ${String(
path || name
)} cannot be a ` + `string id. Use an actual component instead.`
);
}

// 编译正则的选项
const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {};
// 格式化 url,会删除末尾的/,如果route是子级,会连接父级和子级的path,形成一个完整的path
const normalizedPath = normalizePath(
path,
parent,
pathToRegexpOptions.strict
);

// 匹配规则是否大小写敏感?(默认值:false)
if (typeof route.caseSensitive === "boolean") {
pathToRegexpOptions.sensitive = route.caseSensitive;
}

// 生成记录对象
const record: RouteRecord = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
components: route.components || { default: route.component },
instances: {},
name,
parent,
matchAs,
redirect: route.redirect,
beforeEnter: route.beforeEnter,
meta: route.meta || {},
props:
route.props == null
? {}
: route.components
? route.props
: { default: route.props }
};

if (route.children) {
// 递归路由配置的 children 属性,添加路由记录
if (process.env.NODE_ENV !== "production") {
if (
route.name &&
!route.redirect &&
route.children.some(child => /^\/?$/.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(child => {
// matchAs 路由别名
const childMatchAs = matchAs
? cleanPath(`${matchAs}/${child.path}`)
: undefined;
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
});
}

if (!pathMap[record.path]) {
pathList.push(record.path);
pathMap[record.path] = record;
}

// 如果路由有别名的话
// 给别名也添加路由记录
if (route.alias !== undefined) {
const aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
for (let i = 0; i < aliases.length; ++i) {
const alias = aliases[i];
if (process.env.NODE_ENV !== "production" && alias === path) {
warn(
false,
`Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
);
// skip in dev to make it work
continue;
}

const aliasRoute = {
path: alias,
children: route.children
};
addRouteRecord(
pathList,
pathMap,
nameMap,
aliasRoute,
parent,
record.path || "/" // matchAs
);
}
}

// 命名路由添加记录
if (name) {
if (!nameMap[name]) {
nameMap[name] = record;
} else if (process.env.NODE_ENV !== "production" && !matchAs) {
warn(
false,
`Duplicate named routes definition: ` +
`{ name: "${name}", path: "${record.path}" }`
);
}
}
}

normalizePath函数

格式化 url,会删除末尾的/,如果route是子级,会连接父级和子级的path,形成一个完整的path

1
2
3
4
5
6
7
8
9
10
function normalizePath(
path: string,
parent?: RouteRecord,
strict?: boolean
): string {
if (!strict) path = path.replace(/\/$/, "");
if (path[0] === "/") return path;
if (parent == null) return path;
return cleanPath(`${parent.path}/${path}`);
}

cleanPath函数

双斜杠全局替换成单斜杠

1
2
3
export function cleanPath(path: string): string {
return path.replace(/\/\//g, "/");
}