VueRouter 的实现是一个类,先对它做一个简单地分析,它的定义在 src/index.js 中

    1. export default class VueRouter {
    2. static install: () => void
    3. static version: string
    4. static isNavigationFailure: Function
    5. static NavigationFailureType: any
    6. static START_LOCATION: Route
    7. app: any
    8. apps: Array<any>
    9. ready: boolean
    10. readyCbs: Array<Function>
    11. options: RouterOptions
    12. mode: string
    13. history: HashHistory | HTML5History | AbstractHistory
    14. matcher: Matcher
    15. fallback: boolean
    16. beforeHooks: Array<?NavigationGuard>
    17. resolveHooks: Array<?NavigationGuard>
    18. afterHooks: Array<?AfterNavigationHook>
    19. constructor (options: RouterOptions = {}) {
    20. if (process.env.NODE_ENV !== 'production') {
    21. warn(this instanceof VueRouter, `Router must be called with the new operator.`)
    22. }
    23. this.app = null // 根实例
    24. this.apps = [] // 保存持有$options.router属性的Vue实例
    25. this.options = options // 保存传入的路由配置
    26. // 钩子函数
    27. this.beforeHooks = []
    28. this.resolveHooks = []
    29. this.afterHooks = []
    30. // 路由匹配器
    31. this.matcher = createMatcher(options.routes || [], this)
    32. let mode = options.mode || 'hash'
    33. // 浏览器不支持history.pushState的情况下,根据传入fallback配置参数决定是否退回到hash模式
    34. this.fallback =
    35. mode === 'history' && !supportsPushState && options.fallback !== false
    36. if (this.fallback) {
    37. mode = 'hash'
    38. }
    39. if (!inBrowser) {
    40. mode = 'abstract'
    41. }
    42. this.mode = mode // 路由创建的模式
    43. // this.history表示路由历史的具体的实现实例,它是根据this.mode的不同实现不同,它有history基类,然后不同的history实现都是继承History
    44. switch (mode) {
    45. case 'history':
    46. this.history = new HTML5History(this, options.base)
    47. break
    48. case 'hash':
    49. this.history = new HashHistory(this, options.base, this.fallback)
    50. break
    51. case 'abstract':
    52. this.history = new AbstractHistory(this, options.base)
    53. break
    54. default:
    55. if (process.env.NODE_ENV !== 'production') {
    56. assert(false, `invalid mode: ${mode}`)
    57. }
    58. }
    59. }
    60. // 下一节
    61. match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
    62. return this.matcher.match(raw, current, redirectedFrom)
    63. }
    64. get currentRoute (): ?Route {
    65. return this.history && this.history.current
    66. }
    67. // 参数是Vue实例
    68. init (app: any /* Vue component instance */) {
    69. process.env.NODE_ENV !== 'production' &&
    70. assert(
    71. install.installed,
    72. `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
    73. `before creating root instance.`
    74. )
    75. // 存储到this.apps
    76. this.apps.push(app) // 只有根 Vue 实例会保存到 this.app 中,并且会拿到当前的 this.history,根据它的不同类型来执行不同逻辑
    77. // set up app destroyed handler
    78. // https://github.com/vuejs/vue-router/issues/2639
    79. app.$once('hook:destroyed', () => {
    80. // clean out app from this.apps array once destroyed
    81. const index = this.apps.indexOf(app)
    82. if (index > -1) this.apps.splice(index, 1)
    83. // ensure we still have a main app or null if no apps
    84. // we do not release the router so it can be reused
    85. if (this.app === app) this.app = this.apps[0] || null
    86. if (!this.app) this.history.teardown()
    87. })
    88. // main app previously initialized
    89. // return as we don't need to set up new history listener
    90. if (this.app) {
    91. return
    92. }
    93. this.app = app
    94. const history = this.history
    95. if (history instanceof HTML5History || history instanceof HashHistory) {
    96. const handleInitialScroll = routeOrError => {
    97. const from = history.current
    98. const expectScroll = this.options.scrollBehavior
    99. const supportsScroll = supportsPushState && expectScroll
    100. if (supportsScroll && 'fullPath' in routeOrError) {
    101. handleScroll(this, routeOrError, from, false)
    102. }
    103. }
    104. const setupListeners = routeOrError => {
    105. history.setupListeners()
    106. handleInitialScroll(routeOrError)
    107. }
    108. // 定义在History的基类中
    109. history.transitionTo(
    110. history.getCurrentLocation(),
    111. setupListeners,
    112. setupListeners
    113. )
    114. }
    115. history.listen(route => {
    116. this.apps.forEach(app => {
    117. app._route = route
    118. })
    119. })
    120. }
    121. beforeEach (fn: Function): Function {
    122. return registerHook(this.beforeHooks, fn)
    123. }
    124. beforeResolve (fn: Function): Function {
    125. return registerHook(this.resolveHooks, fn)
    126. }
    127. afterEach (fn: Function): Function {
    128. return registerHook(this.afterHooks, fn)
    129. }
    130. onReady (cb: Function, errorCb?: Function) {
    131. this.history.onReady(cb, errorCb)
    132. }
    133. onError (errorCb: Function) {
    134. this.history.onError(errorCb)
    135. }
    136. push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    137. // $flow-disable-line
    138. if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
    139. return new Promise((resolve, reject) => {
    140. this.history.push(location, resolve, reject)
    141. })
    142. } else {
    143. this.history.push(location, onComplete, onAbort)
    144. }
    145. }
    146. replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    147. // $flow-disable-line
    148. if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
    149. return new Promise((resolve, reject) => {
    150. this.history.replace(location, resolve, reject)
    151. })
    152. } else {
    153. this.history.replace(location, onComplete, onAbort)
    154. }
    155. }
    156. go (n: number) {
    157. this.history.go(n)
    158. }
    159. back () {
    160. this.go(-1)
    161. }
    162. forward () {
    163. this.go(1)
    164. }
    165. getMatchedComponents (to?: RawLocation | Route): Array<any> {
    166. const route: any = to
    167. ? to.matched
    168. ? to
    169. : this.resolve(to).route
    170. : this.currentRoute
    171. if (!route) {
    172. return []
    173. }
    174. return [].concat.apply(
    175. [],
    176. route.matched.map(m => {
    177. return Object.keys(m.components).map(key => {
    178. return m.components[key]
    179. })
    180. })
    181. )
    182. }
    183. resolve (
    184. to: RawLocation,
    185. current?: Route,
    186. append?: boolean
    187. ): {
    188. location: Location,
    189. route: Route,
    190. href: string,
    191. // for backwards compat
    192. normalizedTo: Location,
    193. resolved: Route
    194. } {
    195. current = current || this.history.current
    196. const location = normalizeLocation(to, current, append, this)
    197. const route = this.match(location, current)
    198. const fullPath = route.redirectedFrom || route.fullPath
    199. const base = this.history.base
    200. const href = createHref(base, fullPath, this.mode)
    201. return {
    202. location,
    203. route,
    204. href,
    205. // for backwards compat
    206. normalizedTo: location,
    207. resolved: route
    208. }
    209. }
    210. getRoutes () {
    211. return this.matcher.getRoutes()
    212. }
    213. addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {
    214. this.matcher.addRoute(parentOrRoute, route)
    215. if (this.history.current !== START) {
    216. this.history.transitionTo(this.history.getCurrentLocation())
    217. }
    218. }
    219. addRoutes (routes: Array<RouteConfig>) {
    220. if (process.env.NODE_ENV !== 'production') {
    221. warn(false, 'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')
    222. }
    223. this.matcher.addRoutes(routes)
    224. if (this.history.current !== START) {
    225. this.history.transitionTo(this.history.getCurrentLocation())
    226. }
    227. }
    228. }

    实例化 VueRouter 后会返回它的实例 router,在 new Vue 的时候会把 router 作为配置的属性传入
    上一节讲 beforeCreate 混入的时候有这么一段代码

    1. beforeCreate() {
    2. if (isDef(this.$options.router)) {
    3. // ...
    4. this._router = this.$options.router
    5. this._router.init(this)
    6. // ...
    7. }
    8. }

    所以组件在执行 beforeCreate 钩子函数的时候,如果传入了 router 实例,都会执行 router.init 方法
    history.transitionTo代码在 src/history/base.js

    1. transitionTo (
    2. location: RawLocation,
    3. onComplete?: Function,
    4. onAbort?: Function
    5. ) {
    6. let route
    7. // catch redirect option https://github.com/vuejs/vue-router/issues/3201
    8. try {
    9. // 调用了this.matcher.match方法去做匹配
    10. route = this.router.match(location, this.current)
    11. } catch (e) {
    12. this.errorCbs.forEach(cb => {
    13. cb(e)
    14. })
    15. // Exception should still be thrown
    16. throw e
    17. }
    18. // ...
    19. }

    大致对 VueRouter 类有了大致了解,知道了它的一些属性和方法,同时了解到在组件的初始化阶段,执行到 beforeCreate 钩子函数的时候会执行 router.init 方法,然后又会执行 history.transitionTo 方法做路由过渡,进而引出了 matcher 的概念