VueRouter 构造函数中的 this.matcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default class VueRouter {
static install: () => void;
static version: string;

app: any; // Vue 实例
apps: Array<any>;
ready: boolean;
readyCbs: Array<Function>;
options: RouterOptions;
mode: string; // 路由使用的模式
history: HashHistory | HTML5History | AbstractHistory;
matcher: Matcher;
fallback: boolean;
beforeHooks: Array<?NavigationGuard>;
resolveHooks: Array<?NavigationGuard>;
afterHooks: Array<?AfterNavigationHook>;

constructor (options: RouterOptions = {}) {
// code...
// 生成路由匹配
this.matcher = createMatcher(options.routes || [], this)

// code...
}

构造函数中的 this.matcher = createMatcher(options.routes || [], this) 会根据用户传入的路由信息构造出路由匹配,VueRouter 的路由对象就是由它内部生成的。

createMatcher

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
export function createMatcher (
routes: Array<RouteConfig>, // 配置的路由信息
router: VueRouter // Vue-Router 实例
): Matcher {
// 创建 route map,然后返回 pathList, pathMap, nameMap 供下次 addRoutes 使用
const { pathList, pathMap, nameMap } = createRouteMap(routes)

// 添加路由
function addRoutes (routes) {
createRouteMap(routes, pathList, pathMap, nameMap)
}

// 返回对应路由信息
function match (
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {
const location = normalizeLocation(raw, currentRoute, false, router)
const { name } = location

if (name) {
const record = nameMap[name]
if (process.env.NODE_ENV !== 'production') {
warn(record, `Route with name '${name}' does not exist`)
}
if (!record) return _createRoute(null, location)
const paramNames = record.regex.keys
.filter(key => !key.optional)
.map(key => key.name)

if (typeof location.params !== 'object') {
location.params = {}
}

if (currentRoute && typeof currentRoute.params === 'object') {
for (const key in currentRoute.params) {
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
location.params[key] = currentRoute.params[key]
}
}
}

location.path = fillParams(record.path, location.params, `named route "${name}"`)
return _createRoute(record, location, redirectedFrom)
} else if (location.path) {
location.params = {}
for (let i = 0; i < pathList.length; i++) {
const path = pathList[i]
const record = pathMap[path]
if (matchRoute(record.regex, location.path, location.params)) {
return _createRoute(record, location, redirectedFrom)
}
}
}
// no match
return _createRoute(null, location)
}

function redirect (
record: RouteRecord,
location: Location
): Route {
const originalRedirect = record.redirect
let redirect = typeof originalRedirect === 'function'
? originalRedirect(createRoute(record, location, null, router))
: originalRedirect

if (typeof redirect === 'string') {
redirect = { path: redirect }
}

if (!redirect || typeof redirect !== 'object') {
if (process.env.NODE_ENV !== 'production') {
warn(
false, `invalid redirect option: ${JSON.stringify(redirect)}`
)
}
return _createRoute(null, location)
}

const re: Object = redirect
const { name, path } = re
let { query, hash, params } = location
query = re.hasOwnProperty('query') ? re.query : query
hash = re.hasOwnProperty('hash') ? re.hash : hash
params = re.hasOwnProperty('params') ? re.params : params

if (name) {
// resolved named direct
const targetRecord = nameMap[name]
if (process.env.NODE_ENV !== 'production') {
assert(targetRecord, `redirect failed: named route "${name}" not found.`)
}
return match({
_normalized: true,
name,
query,
hash,
params
}, undefined, location)
} else if (path) {
// 1. resolve relative redirect
const rawPath = resolveRecordPath(path, record)
// 2. resolve params
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
// 3. rematch with existing query and hash
return match({
_normalized: true,
path: resolvedPath,
query,
hash
}, undefined, location)
} else {
if (process.env.NODE_ENV !== 'production') {
warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
}
return _createRoute(null, location)
}
}

function alias (
record: RouteRecord,
location: Location,
matchAs: string
): Route {
const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
const aliasedMatch = match({
_normalized: true,
path: aliasedPath
})
if (aliasedMatch) {
const matched = aliasedMatch.matched
const aliasedRecord = matched[matched.length - 1]
location.params = aliasedMatch.params
return _createRoute(aliasedRecord, location)
}
return _createRoute(null, location)
}

function _createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route {
if (record && record.redirect) {
return redirect(record, redirectedFrom || location)
}
if (record && record.matchAs) {
return alias(record, location, record.matchAs)
}
return createRoute(record, location, redirectedFrom, router)
}

return {
match,
addRoutes
}
}

createMatcher() 返回两个方法。match 用来返回匹配到的路由信息。addRoutes 用于动态添加路由。
createMatcher() 第一次被调用的时候就会调用 createRouteMap,用来初始化初始路由,返回 pathList, pathMap, nameMap。
当动态添加路由时仍调用 createRouteMap 方法,并以之前调用该方法的返回值为参数传入。

1
2
3
4
5
6
7
8
// 创建 route map,然后返回 pathList, pathMap, nameMap 供下次 addRoutes 使用
const { pathList, pathMap, nameMap } = createRouteMap(routes)

// 添加路由
function addRoutes (routes) {
// 传入之前调用 createRouteMap 的返回值
createRouteMap(routes, pathList, pathMap, nameMap)
}

createRouteMap

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
// 创建 route map
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 || []
// $flow-disable-line
const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
// $flow-disable-line
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)

// 遍历所有传入的 routes 数据,调用 addRouteRecord 添加路由记录
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--
}
}

return {
pathList,
pathMap,
nameMap
}
}

createRouteMap 中遍历所有传入的 routes 数据,调用 addRouteRecord 添加路由记录。

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
// 添加路由记录
function addRouteRecord (
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>,
route: RouteConfig,
parent?: RouteRecord,
matchAs?: string
) {
const { path, name } = route

// code...

const pathToRegexpOptions: PathToRegexpOptions =
route.pathToRegexpOptions || {}
const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)

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) {
// code...
}

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

// 处理路由别名
if (route.alias !== undefined) {
// code...
}

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}" }`
)
}
}
}

addRouteRecord 方法内首先会构造一个路由记录 record,如果存在子路由则递归调用 addRouteRecord。最后做对应处理后分别放入 addRouteRecord 的入参 pathMap 与 nameMap 中,由于引用传递就直接修改了外部的 pathMap 与 nameMap。

End