• VueRouter 对象
    • 总结

    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