# VueRouter 对象
VueRouter 的实现是一个类,我们先对它做一个简单地分析,它的定义在 src/index.js
中:
export default class VueRouter {
static install: () => void;
static version: string;
app: any;
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 = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
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}`)
}
}
}
match (
raw: RawLocation,
current?: Route,
redirectedFrom?: Location
): Route {
return this.matcher.match(raw, current, redirectedFrom)
}
get currentRoute (): ?Route {
return this.history && this.history.current
}
init (app: any) {
process.env.NODE_ENV !== 'production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app)
if (this.app) {
return
}
this.app = app
const history = this.history
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
beforeEach (fn: Function): Function {
return registerHook(this.beforeHooks, fn)
}
beforeResolve (fn: Function): Function {
return registerHook(this.resolveHooks, fn)
}
afterEach (fn: Function): Function {
return registerHook(this.afterHooks, fn)
}
onReady (cb: Function, errorCb?: Function) {
this.history.onReady(cb, errorCb)
}
onError (errorCb: Function) {
this.history.onError(errorCb)
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.push(location, onComplete, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.replace(location, onComplete, onAbort)
}
go (n: number) {
this.history.go(n)
}
back () {
this.go(-1)
}
forward () {
this.go(1)
}
getMatchedComponents (to?: RawLocation | Route): Array<any> {
const route: any = to
? to.matched
? to
: this.resolve(to).route
: this.currentRoute
if (!route) {
return []
}
return [].concat.apply([], route.matched.map(m => {
return Object.keys(m.components).map(key => {
return m.components[key]
})
}))
}
resolve (
to: RawLocation,
current?: Route,
append?: boolean
): {
location: Location,
route: Route,
href: string,
normalizedTo: Location,
resolved: Route
} {
const location = normalizeLocation(
to,
current || this.history.current,
append,
this
)
const route = this.match(location, current)
const fullPath = route.redirectedFrom || route.fullPath
const base = this.history.base
const href = createHref(base, fullPath, this.mode)
return {
location,
route,
href,
normalizedTo: location,
resolved: route
}
}
addRoutes (routes: Array<RouteConfig>) {
this.matcher.addRoutes(routes)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
}
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
VueRouter
定义了一些属性和方法,我们先从它的构造函数看,当我们执行 new VueRouter
的时候做了哪些事情。
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
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}`)
}
}
}
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
构造函数定义了一些属性,其中 this.app
表示根 Vue
实例,this.apps
保存持有 $options.router
属性的 Vue
实例,this.options
保存传入的路由配置,this.beforeHooks
、
this.resolveHooks
、this.afterHooks
表示一些钩子函数,我们之后会介绍,this.matcher
表示路由匹配器,我们之后会介绍,this.fallback
表示在浏览器不支持 history.pushState
的情况下,根据传入的 fallback
配置参数,决定是否回退到hash模式,this.mode
表示路由创建的模式,this.history
表示路由历史的具体的实现实例,它是根据 this.mode
的不同实现不同,它有 History
基类,然后不同的 history
实现都是继承 History
。
实例化 VueRouter
后会返回它的实例 router
,我们在 new Vue
的时候会把 router
作为配置的属性传入,回顾一下上一节我们讲 beforeCreate
混入的时候有这么一段代码:
beforeCreate() {
if (isDef(this.$options.router)) {
// ...
this._router = this.$options.router
this._router.init(this)
// ...
}
}
2
3
4
5
6
7
8
所以组件在执行 beforeCreate
钩子函数的时候,如果传入了 router
实例,都会执行 router.init
方法:
init (app: any) {
process.env.NODE_ENV !== 'production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app)
if (this.app) {
return
}
this.app = app
const history = this.history
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
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
init
的逻辑很简单,它传入的参数是 Vue
实例,然后存储到 this.apps
中;只有根 Vue
实例会保存到 this.app
中,并且会拿到当前的 this.history
,根据它的不同类型来执行不同逻辑,由于我们平时使用 hash
路由多一些,所以我们先看这部分逻辑,先定义了 setupHashListener
函数,接着执行了 history.transitionTo
方法,它是定义在 History
基类中,代码在 src/history/base.js
:
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const route = this.router.match(location, this.current)
// ...
}
2
3
4
我们先不着急去看 transitionTo
的具体实现,先看第一行代码,它调用了 this.router.match
函数:
match (
raw: RawLocation,
current?: Route,
redirectedFrom?: Location
): Route {
return this.matcher.match(raw, current, redirectedFrom)
}
2
3
4
5
6
7
实际上是调用了 this.matcher.match
方法去做匹配,所以接下来我们先来了解一下 matcher
的相关实现。
# 总结
通过这一节的分析,我们大致对 VueRouter
类有了大致了解,知道了它的一些属性和方法,同时了解到在组件的初始化阶段,执行到 beforeCreate
钩子函数的时候会执行 router.init
方法,然后又会执行 history.transitionTo
方法做路由过渡,进而引出了 matcher
的概念,接下来我们先研究一下 matcher
的相关实现。