22bb2f3eb406cf0e38a2e9a95b7a128e0930f2b3e7f99f0a9eaf292f35acbf06167669467d977db91e3d6b4f8e4e602a77be63cbb321014b96d6f63a9dd0eb 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /* @flow */
  2. import type VueRouter from '../index'
  3. import { stringifyQuery } from './query'
  4. const trailingSlashRE = /\/?$/
  5. export function createRoute (
  6. record: ?RouteRecord,
  7. location: Location,
  8. redirectedFrom?: ?Location,
  9. router?: VueRouter
  10. ): Route {
  11. const stringifyQuery = router && router.options.stringifyQuery
  12. let query: any = location.query || {}
  13. try {
  14. query = clone(query)
  15. } catch (e) {}
  16. const route: Route = {
  17. name: location.name || (record && record.name),
  18. meta: (record && record.meta) || {},
  19. path: location.path || '/',
  20. hash: location.hash || '',
  21. query,
  22. params: location.params || {},
  23. fullPath: getFullPath(location, stringifyQuery),
  24. matched: record ? formatMatch(record) : []
  25. }
  26. if (redirectedFrom) {
  27. route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
  28. }
  29. return Object.freeze(route)
  30. }
  31. function clone (value) {
  32. if (Array.isArray(value)) {
  33. return value.map(clone)
  34. } else if (value && typeof value === 'object') {
  35. const res = {}
  36. for (const key in value) {
  37. res[key] = clone(value[key])
  38. }
  39. return res
  40. } else {
  41. return value
  42. }
  43. }
  44. // the starting route that represents the initial state
  45. export const START = createRoute(null, {
  46. path: '/'
  47. })
  48. function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
  49. const res = []
  50. while (record) {
  51. res.unshift(record)
  52. record = record.parent
  53. }
  54. return res
  55. }
  56. function getFullPath (
  57. { path, query = {}, hash = '' },
  58. _stringifyQuery
  59. ): string {
  60. const stringify = _stringifyQuery || stringifyQuery
  61. return (path || '/') + stringify(query) + hash
  62. }
  63. export function isSameRoute (a: Route, b: ?Route): boolean {
  64. if (b === START) {
  65. return a === b
  66. } else if (!b) {
  67. return false
  68. } else if (a.path && b.path) {
  69. return (
  70. a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
  71. a.hash === b.hash &&
  72. isObjectEqual(a.query, b.query)
  73. )
  74. } else if (a.name && b.name) {
  75. return (
  76. a.name === b.name &&
  77. a.hash === b.hash &&
  78. isObjectEqual(a.query, b.query) &&
  79. isObjectEqual(a.params, b.params)
  80. )
  81. } else {
  82. return false
  83. }
  84. }
  85. function isObjectEqual (a = {}, b = {}): boolean {
  86. // handle null value #1566
  87. if (!a || !b) return a === b
  88. const aKeys = Object.keys(a).sort()
  89. const bKeys = Object.keys(b).sort()
  90. if (aKeys.length !== bKeys.length) {
  91. return false
  92. }
  93. return aKeys.every((key, i) => {
  94. const aVal = a[key]
  95. const bKey = bKeys[i]
  96. if (bKey !== key) return false
  97. const bVal = b[key]
  98. // query values can be null and undefined
  99. if (aVal == null || bVal == null) return aVal === bVal
  100. // check nested equality
  101. if (typeof aVal === 'object' && typeof bVal === 'object') {
  102. return isObjectEqual(aVal, bVal)
  103. }
  104. return String(aVal) === String(bVal)
  105. })
  106. }
  107. export function isIncludedRoute (current: Route, target: Route): boolean {
  108. return (
  109. current.path.replace(trailingSlashRE, '/').indexOf(
  110. target.path.replace(trailingSlashRE, '/')
  111. ) === 0 &&
  112. (!target.hash || current.hash === target.hash) &&
  113. queryIncludes(current.query, target.query)
  114. )
  115. }
  116. function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
  117. for (const key in target) {
  118. if (!(key in current)) {
  119. return false
  120. }
  121. }
  122. return true
  123. }
  124. export function handleRouteEntered (route: Route) {
  125. for (let i = 0; i < route.matched.length; i++) {
  126. const record = route.matched[i]
  127. for (const name in record.instances) {
  128. const instance = record.instances[name]
  129. const cbs = record.enteredCbs[name]
  130. if (!instance || !cbs) continue
  131. delete record.enteredCbs[name]
  132. for (let i = 0; i < cbs.length; i++) {
  133. if (!instance._isBeingDestroyed) cbs[i](instance)
  134. }
  135. }
  136. }
  137. }