VueRouter 对象

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

  1. export default class VueRouter {
  2. static install: () => void;
  3. static version: string;
  4. app: any;
  5. apps: Array<any>;
  6. ready: boolean;
  7. readyCbs: Array<Function>;
  8. options: RouterOptions;
  9. mode: string;
  10. history: HashHistory | HTML5History | AbstractHistory;
  11. matcher: Matcher;
  12. fallback: boolean;
  13. beforeHooks: Array<?NavigationGuard>;
  14. resolveHooks: Array<?NavigationGuard>;
  15. afterHooks: Array<?AfterNavigationHook>;
  16. constructor (options: RouterOptions = {}) {
  17. this.app = null
  18. this.apps = []
  19. this.options = options
  20. this.beforeHooks = []
  21. this.resolveHooks = []
  22. this.afterHooks = []
  23. this.matcher = createMatcher(options.routes || [], this)
  24. let mode = options.mode || 'hash'
  25. this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
  26. if (this.fallback) {
  27. mode = 'hash'
  28. }
  29. if (!inBrowser) {
  30. mode = 'abstract'
  31. }
  32. this.mode = mode
  33. switch (mode) {
  34. case 'history':
  35. this.history = new HTML5History(this, options.base)
  36. break
  37. case 'hash':
  38. this.history = new HashHistory(this, options.base, this.fallback)
  39. break
  40. case 'abstract':
  41. this.history = new AbstractHistory(this, options.base)
  42. break
  43. default:
  44. if (process.env.NODE_ENV !== 'production') {
  45. assert(false, `invalid mode: ${mode}`)
  46. }
  47. }
  48. }
  49. match (
  50. raw: RawLocation,
  51. current?: Route,
  52. redirectedFrom?: Location
  53. ): Route {
  54. return this.matcher.match(raw, current, redirectedFrom)
  55. }
  56. get currentRoute (): ?Route {
  57. return this.history && this.history.current
  58. }
  59. init (app: any) {
  60. process.env.NODE_ENV !== 'production' && assert(
  61. install.installed,
  62. `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
  63. `before creating root instance.`
  64. )
  65. this.apps.push(app)
  66. if (this.app) {
  67. return
  68. }
  69. this.app = app
  70. const history = this.history
  71. if (history instanceof HTML5History) {
  72. history.transitionTo(history.getCurrentLocation())
  73. } else if (history instanceof HashHistory) {
  74. const setupHashListener = () => {
  75. history.setupListeners()
  76. }
  77. history.transitionTo(
  78. history.getCurrentLocation(),
  79. setupHashListener,
  80. setupHashListener
  81. )
  82. }
  83. history.listen(route => {
  84. this.apps.forEach((app) => {
  85. app._route = route
  86. })
  87. })
  88. }
  89. beforeEach (fn: Function): Function {
  90. return registerHook(this.beforeHooks, fn)
  91. }
  92. beforeResolve (fn: Function): Function {
  93. return registerHook(this.resolveHooks, fn)
  94. }
  95. afterEach (fn: Function): Function {
  96. return registerHook(this.afterHooks, fn)
  97. }
  98. onReady (cb: Function, errorCb?: Function) {
  99. this.history.onReady(cb, errorCb)
  100. }
  101. onError (errorCb: Function) {
  102. this.history.onError(errorCb)
  103. }
  104. push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  105. this.history.push(location, onComplete, onAbort)
  106. }
  107. replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  108. this.history.replace(location, onComplete, onAbort)
  109. }
  110. go (n: number) {
  111. this.history.go(n)
  112. }
  113. back () {
  114. this.go(-1)
  115. }
  116. forward () {
  117. this.go(1)
  118. }
  119. getMatchedComponents (to?: RawLocation | Route): Array<any> {
  120. const route: any = to
  121. ? to.matched
  122. ? to
  123. : this.resolve(to).route
  124. : this.currentRoute
  125. if (!route) {
  126. return []
  127. }
  128. return [].concat.apply([], route.matched.map(m => {
  129. return Object.keys(m.components).map(key => {
  130. return m.components[key]
  131. })
  132. }))
  133. }
  134. resolve (
  135. to: RawLocation,
  136. current?: Route,
  137. append?: boolean
  138. ): {
  139. location: Location,
  140. route: Route,
  141. href: string,
  142. normalizedTo: Location,
  143. resolved: Route
  144. } {
  145. const location = normalizeLocation(
  146. to,
  147. current || this.history.current,
  148. append,
  149. this
  150. )
  151. const route = this.match(location, current)
  152. const fullPath = route.redirectedFrom || route.fullPath
  153. const base = this.history.base
  154. const href = createHref(base, fullPath, this.mode)
  155. return {
  156. location,
  157. route,
  158. href,
  159. normalizedTo: location,
  160. resolved: route
  161. }
  162. }
  163. addRoutes (routes: Array<RouteConfig>) {
  164. this.matcher.addRoutes(routes)
  165. if (this.history.current !== START) {
  166. this.history.transitionTo(this.history.getCurrentLocation())
  167. }
  168. }
  169. }

VueRouter 定义了一些属性和方法,我们先从它的构造函数看,当我们执行 new VueRouter 的时候做了哪些事情。

  1. constructor (options: RouterOptions = {}) {
  2. this.app = null
  3. this.apps = []
  4. this.options = options
  5. this.beforeHooks = []
  6. this.resolveHooks = []
  7. this.afterHooks = []
  8. this.matcher = createMatcher(options.routes || [], this)
  9. let mode = options.mode || 'hash'
  10. this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
  11. if (this.fallback) {
  12. mode = 'hash'
  13. }
  14. if (!inBrowser) {
  15. mode = 'abstract'
  16. }
  17. this.mode = mode
  18. switch (mode) {
  19. case 'history':
  20. this.history = new HTML5History(this, options.base)
  21. break
  22. case 'hash':
  23. this.history = new HashHistory(this, options.base, this.fallback)
  24. break
  25. case 'abstract':
  26. this.history = new AbstractHistory(this, options.base)
  27. break
  28. default:
  29. if (process.env.NODE_ENV !== 'production') {
  30. assert(false, `invalid mode: ${mode}`)
  31. }
  32. }
  33. }

构造函数定义了一些属性,其中 this.app 表示根 Vue 实例,this.apps 保存持有 $options.router 属性的 Vue 实例,this.options 保存传入的路由配置,this.beforeHooksthis.resolveHooksthis.afterHooks 表示一些钩子函数,我们之后会介绍,this.matcher 表示路由匹配器,我们之后会介绍,this.fallback 表示在浏览器不支持 history.pushState 的情况下,根据传入的 fallback 配置参数,决定是否回退到hash模式,this.mode 表示路由创建的模式,this.history 表示路由历史的具体的实现实例,它是根据 this.mode 的不同实现不同,它有 History 基类,然后不同的 history 实现都是继承 History

实例化 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 方法:

  1. init (app: any) {
  2. process.env.NODE_ENV !== 'production' && assert(
  3. install.installed,
  4. `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
  5. `before creating root instance.`
  6. )
  7. this.apps.push(app)
  8. if (this.app) {
  9. return
  10. }
  11. this.app = app
  12. const history = this.history
  13. if (history instanceof HTML5History) {
  14. history.transitionTo(history.getCurrentLocation())
  15. } else if (history instanceof HashHistory) {
  16. const setupHashListener = () => {
  17. history.setupListeners()
  18. }
  19. history.transitionTo(
  20. history.getCurrentLocation(),
  21. setupHashListener,
  22. setupHashListener
  23. )
  24. }
  25. history.listen(route => {
  26. this.apps.forEach((app) => {
  27. app._route = route
  28. })
  29. })
  30. }

init 的逻辑很简单,它传入的参数是 Vue 实例,然后存储到 this.apps 中;只有根 Vue 实例会保存到 this.app 中,并且会拿到当前的 this.history,根据它的不同类型来执行不同逻辑,由于我们平时使用 hash 路由多一些,所以我们先看这部分逻辑,先定义了 setupHashListener 函数,接着执行了 history.transitionTo 方法,它是定义在 History 基类中,代码在 src/history/base.js

  1. transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  2. const route = this.router.match(location, this.current)
  3. // ...
  4. }

我们先不着急去看 transitionTo 的具体实现,先看第一行代码,它调用了 this.router.match 函数:

  1. match (
  2. raw: RawLocation,
  3. current?: Route,
  4. redirectedFrom?: Location
  5. ): Route {
  6. return this.matcher.match(raw, current, redirectedFrom)
  7. }

实际上是调用了 this.matcher.match 方法去做匹配,所以接下来我们先来了解一下 matcher 的相关实现。

总结

通过这一节的分析,我们大致对 VueRouter 类有了大致了解,知道了它的一些属性和方法,同时了了解到在组件的初始化阶段,执行到 beforeCreate 钩子函数的时候会执行 router.init 方法,然后又会执行 history.transitionTo 方法做路由过渡,进而引出了 matcher 的概念,接下来我们先研究一下 matcher 的相关实现。

原文: https://ustbhuangyi.github.io/vue-analysis/vue-router/router.html