Vue路由History模式分析

Vue-routerVue的核心组件,主要是作为Vue的路由管理器,Vue-router默认hash模式,通过引入Vue-router对象模块时配置mode属性可以启用history模式。

描述

Vue-routerhash模式使用URLHash来模拟一个完整的URL,当URL改变时页面不会重新加载,而Vue-routerhistory模式是充分利用history.pushStateAPI来完成URL跳转,同样在页面跳转时无须重新加载页面,当然也不会对于服务端进行请求,当然对于history模式仍然是需要后端的配置支持,由于应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问URL时就会返回404,所以需要在服务端增加一个覆盖所有情况的候选资源,如果URL匹配不到任何静态资源时,则应该返回同一个index.html应用依赖页面,例如在Nginx下的配置。

  1. location / {
  2. try_files $uri $uri/ /index.html;
  3. }

分析

Vue-router源码的实现比较复杂,会处理各种兼容问题与异常以及各种条件分支,文章分析比较核心的代码部分,精简过后的版本,重要部分做出注释,commit id560d11d

首先是在定义Router时调用Vue.use(VueRouter),这是Vue.js插件的经典写法,给插件对象增加install方法用来安装插件具体逻辑,此时会调用VueRouter类上的静态方法,即VueRouter.install = installinstall模块主要是保证Vue-router只被use一次,以及通过mixinVue的生命周期beforeCreate内注册实例,在destroyed内销毁实例,还有定义$router$route属性为只读属性以及<router-view><router-link>全局组件的注册。

  1. // dev/src/install.js line 6
  2. export function install (Vue) {
  3. if (install.installed && _Vue === Vue) return
  4. install.installed = true // 保证 Vue-router 只被 use 一次
  5. _Vue = Vue
  6. const isDef = v => v !== undefined
  7. const registerInstance = (vm, callVal) => {
  8. let i = vm.$options._parentVnode
  9. if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
  10. i(vm, callVal)
  11. }
  12. }
  13. Vue.mixin({
  14. beforeCreate () { // 注册实例
  15. if (isDef(this.$options.router)) { // this.$options.router 来自于 VueRouter 的实例化 // 判断实例是否已经挂载
  16. this._routerRoot = this
  17. this._router = this.$options.router
  18. this._router.init(this) // // 调用 VueRouter 的 init 方法 // 后文会说明 init 方法的作用
  19. Vue.util.defineReactive(this, '_route', this._router.history.current)
  20. } else {
  21. this._routerRoot = (this.$parent && this.$parent._routerRoot) || this // 将组件的 _routerRoot 都指向根 Vue 实例
  22. }
  23. registerInstance(this, this)
  24. },
  25. destroyed () { // 销毁实例 即挂载undefined
  26. registerInstance(this)
  27. }
  28. })
  29. Object.defineProperty(Vue.prototype, '$router', {
  30. get () { return this._routerRoot._router }
  31. })
  32. Object.defineProperty(Vue.prototype, '$route', {
  33. get () { return this._routerRoot._route }
  34. })
  35. Vue.component('RouterView', View) // 注册全局组件 <router-view>
  36. Vue.component('RouterLink', Link) // 注册全局组件 <router-link>
  37. const strats = Vue.config.optionMergeStrategies
  38. // use the same hook merging strategy for route hooks
  39. strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
  40. }

之后是VueRouter对象的构造函数,主要是先获取mode的值,如果mode的值为history但是浏览器不支持history模式,那么就强制设置mode值为hash,接下来根据mode的值,来选择vue-router使用哪种模式。

  1. // dev/src/index.js line 40
  2. constructor (options: RouterOptions = {}) {
  3. this.app = null
  4. this.apps = []
  5. this.options = options
  6. this.beforeHooks = []
  7. this.resolveHooks = []
  8. this.afterHooks = []
  9. this.matcher = createMatcher(options.routes || [], this) // 创建路由匹配对象
  10. let mode = options.mode || 'hash'
  11. this.fallback =
  12. mode === 'history' && !supportsPushState && options.fallback !== false // 检车兼容
  13. if (this.fallback) {
  14. mode = 'hash'
  15. }
  16. if (!inBrowser) {
  17. mode = 'abstract'
  18. }
  19. this.mode = mode
  20. switch (mode) {
  21. case 'history':
  22. this.history = new HTML5History(this, options.base) // 实例化history模式
  23. break
  24. case 'hash':
  25. this.history = new HashHistory(this, options.base, this.fallback) // 实例化Hash模式
  26. break
  27. case 'abstract':
  28. this.history = new AbstractHistory(this, options.base)
  29. break
  30. default:
  31. if (process.env.NODE_ENV !== 'production') {
  32. assert(false, `invalid mode: ${mode}`)
  33. }
  34. }
  35. }

在构造函数中调用了创建路由匹配对象的方法createMatcher,而在createMatcher中又调用了实际用以创建路由映射表的方法createRouteMap,可以说createMatcher函数的作用就是创建路由映射表,然后通过闭包的方式让addRoutesmatch函数能够使用路由映射表的几个对象,最后返回一个Matcher对象。

  1. // dev/src/create-matcher.js line 16
  2. export function createMatcher (
  3. routes: Array<RouteConfig>,
  4. router: VueRouter
  5. ): Matcher {
  6. const { pathList, pathMap, nameMap } = createRouteMap(routes) // 创建路由映射表
  7. function addRoutes (routes) {
  8. createRouteMap(routes, pathList, pathMap, nameMap)
  9. }
  10. function match ( // 路由匹配
  11. raw: RawLocation,
  12. currentRoute?: Route,
  13. redirectedFrom?: Location
  14. ): Route {
  15. const location = normalizeLocation(raw, currentRoute, false, router) // location 是一个对象,类似于 {"_normalized":true,"path":"/","query":{},"hash":""}
  16. const { name } = location
  17. if (name) { // 如果有路由名称 就进行nameMap映射
  18. const record = nameMap[name] // nameMap[name] = 路由记录
  19. if (process.env.NODE_ENV !== 'production') {
  20. warn(record, `Route with name '${name}' does not exist`)
  21. }
  22. if (!record) return _createRoute(null, location)
  23. const paramNames = record.regex.keys
  24. .filter(key => !key.optional)
  25. .map(key => key.name)
  26. if (typeof location.params !== 'object') {
  27. location.params = {}
  28. }
  29. if (currentRoute && typeof currentRoute.params === 'object') {
  30. for (const key in currentRoute.params) {
  31. if (!(key in location.params) && paramNames.indexOf(key) > -1) {
  32. location.params[key] = currentRoute.params[key]
  33. }
  34. }
  35. }
  36. location.path = fillParams(record.path, location.params, `named route "${name}"`)
  37. return _createRoute(record, location, redirectedFrom)
  38. } else if (location.path) { // 如果路由配置了path,到pathList和PathMap里匹配到路由记录
  39. location.params = {}
  40. for (let i = 0; i < pathList.length; i++) {
  41. const path = pathList[i]
  42. const record = pathMap[path]
  43. if (matchRoute(record.regex, location.path, location.params)) {
  44. return _createRoute(record, location, redirectedFrom)
  45. }
  46. }
  47. }
  48. // no match
  49. return _createRoute(null, location)
  50. }
  51. function redirect ( // 处理重定向
  52. record: RouteRecord,
  53. location: Location
  54. ): Route {
  55. const originalRedirect = record.redirect
  56. let redirect = typeof originalRedirect === 'function'
  57. ? originalRedirect(createRoute(record, location, null, router))
  58. : originalRedirect
  59. if (typeof redirect === 'string') {
  60. redirect = { path: redirect }
  61. }
  62. if (!redirect || typeof redirect !== 'object') {
  63. if (process.env.NODE_ENV !== 'production') {
  64. warn(
  65. false, `invalid redirect option: ${JSON.stringify(redirect)}`
  66. )
  67. }
  68. return _createRoute(null, location)
  69. }
  70. const re: Object = redirect
  71. const { name, path } = re
  72. let { query, hash, params } = location
  73. query = re.hasOwnProperty('query') ? re.query : query
  74. hash = re.hasOwnProperty('hash') ? re.hash : hash
  75. params = re.hasOwnProperty('params') ? re.params : params
  76. if (name) {
  77. // resolved named direct
  78. const targetRecord = nameMap[name]
  79. if (process.env.NODE_ENV !== 'production') {
  80. assert(targetRecord, `redirect failed: named route "${name}" not found.`)
  81. }
  82. return match({
  83. _normalized: true,
  84. name,
  85. query,
  86. hash,
  87. params
  88. }, undefined, location)
  89. } else if (path) {
  90. // 1. resolve relative redirect
  91. const rawPath = resolveRecordPath(path, record)
  92. // 2. resolve params
  93. const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
  94. // 3. rematch with existing query and hash
  95. return match({
  96. _normalized: true,
  97. path: resolvedPath,
  98. query,
  99. hash
  100. }, undefined, location)
  101. } else {
  102. if (process.env.NODE_ENV !== 'production') {
  103. warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
  104. }
  105. return _createRoute(null, location)
  106. }
  107. }
  108. function alias ( // 处理别名
  109. record: RouteRecord,
  110. location: Location,
  111. matchAs: string
  112. ): Route {
  113. const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
  114. const aliasedMatch = match({
  115. _normalized: true,
  116. path: aliasedPath
  117. })
  118. if (aliasedMatch) {
  119. const matched = aliasedMatch.matched
  120. const aliasedRecord = matched[matched.length - 1]
  121. location.params = aliasedMatch.params
  122. return _createRoute(aliasedRecord, location)
  123. }
  124. return _createRoute(null, location)
  125. }
  126. function _createRoute ( // 创建路由
  127. record: ?RouteRecord,
  128. location: Location,
  129. redirectedFrom?: Location
  130. ): Route {
  131. if (record && record.redirect) {
  132. return redirect(record, redirectedFrom || location)
  133. }
  134. if (record && record.matchAs) {
  135. return alias(record, location, record.matchAs)
  136. }
  137. return createRoute(record, location, redirectedFrom, router) // 创建路由对象
  138. }
  139. return {
  140. match,
  141. addRoutes
  142. }
  143. }
  144. // dev/src/create-route-map.js line 7
  145. export function createRouteMap (
  146. routes: Array<RouteConfig>,
  147. oldPathList?: Array<string>,
  148. oldPathMap?: Dictionary<RouteRecord>,
  149. oldNameMap?: Dictionary<RouteRecord>
  150. ): {
  151. pathList: Array<string>,
  152. pathMap: Dictionary<RouteRecord>,
  153. nameMap: Dictionary<RouteRecord>
  154. } {
  155. // the path list is used to control path matching priority
  156. const pathList: Array<string> = oldPathList || [] // 创建映射表
  157. // $flow-disable-line
  158. const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  159. // $flow-disable-line
  160. const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
  161. routes.forEach(route => { // 遍历路由配置,为每个配置添加路由记录
  162. addRouteRecord(pathList, pathMap, nameMap, route)
  163. })
  164. // ensure wildcard routes are always at the end
  165. for (let i = 0, l = pathList.length; i < l; i++) { // 确保通配符在最后
  166. if (pathList[i] === '*') {
  167. pathList.push(pathList.splice(i, 1)[0])
  168. l--
  169. i--
  170. }
  171. }
  172. if (process.env.NODE_ENV === 'development') {
  173. // warn if routes do not include leading slashes
  174. const found = pathList
  175. // check for missing leading slash
  176. .filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/')
  177. if (found.length > 0) {
  178. const pathNames = found.map(path => `- ${path}`).join('\n')
  179. warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)
  180. }
  181. }
  182. return {
  183. pathList,
  184. pathMap,
  185. nameMap
  186. }
  187. }
  188. function addRouteRecord ( // 添加路由记录
  189. pathList: Array<string>,
  190. pathMap: Dictionary<RouteRecord>,
  191. nameMap: Dictionary<RouteRecord>,
  192. route: RouteConfig,
  193. parent?: RouteRecord,
  194. matchAs?: string
  195. ) {
  196. const { path, name } = route // 获得路由配置下的属性
  197. if (process.env.NODE_ENV !== 'production') {
  198. assert(path != null, `"path" is required in a route configuration.`)
  199. assert(
  200. typeof route.component !== 'string',
  201. `route config "component" for path: ${String(
  202. path || name
  203. )} cannot be a ` + `string id. Use an actual component instead.`
  204. )
  205. }
  206. const pathToRegexpOptions: PathToRegexpOptions =
  207. route.pathToRegexpOptions || {}
  208. const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
  209. if (typeof route.caseSensitive === 'boolean') {
  210. pathToRegexpOptions.sensitive = route.caseSensitive
  211. }
  212. const record: RouteRecord = { // 生成记录对象
  213. path: normalizedPath,
  214. regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
  215. components: route.components || { default: route.component },
  216. instances: {},
  217. name,
  218. parent,
  219. matchAs,
  220. redirect: route.redirect,
  221. beforeEnter: route.beforeEnter,
  222. meta: route.meta || {},
  223. props:
  224. route.props == null
  225. ? {}
  226. : route.components
  227. ? route.props
  228. : { default: route.props }
  229. }
  230. if (route.children) {
  231. // Warn if route is named, does not redirect and has a default child route.
  232. // If users navigate to this route by name, the default child will
  233. // not be rendered (GH Issue #629)
  234. if (process.env.NODE_ENV !== 'production') {
  235. if (
  236. route.name &&
  237. !route.redirect &&
  238. route.children.some(child => /^\/?$/.test(child.path))
  239. ) {
  240. warn(
  241. false,
  242. `Named Route '${route.name}' has a default child route. ` +
  243. `When navigating to this named route (:to="{name: '${
  244. route.name
  245. }'"), ` +
  246. `the default child route will not be rendered. Remove the name from ` +
  247. `this route and use the name of the default child route for named ` +
  248. `links instead.`
  249. )
  250. }
  251. }
  252. route.children.forEach(child => { // 递归路由配置的 children 属性,添加路由记录
  253. const childMatchAs = matchAs
  254. ? cleanPath(`${matchAs}/${child.path}`)
  255. : undefined
  256. addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
  257. })
  258. }
  259. if (!pathMap[record.path]) { // 如果有多个相同的路径,只有第一个起作用,后面的会被忽略
  260. pathList.push(record.path)
  261. pathMap[record.path] = record
  262. }
  263. if (route.alias !== undefined) { // 如果路由有别名的话,给别名也添加路由记录
  264. const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]
  265. for (let i = 0; i < aliases.length; ++i) {
  266. const alias = aliases[i]
  267. if (process.env.NODE_ENV !== 'production' && alias === path) {
  268. warn(
  269. false,
  270. `Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
  271. )
  272. // skip in dev to make it work
  273. continue
  274. }
  275. const aliasRoute = {
  276. path: alias,
  277. children: route.children
  278. }
  279. addRouteRecord(
  280. pathList,
  281. pathMap,
  282. nameMap,
  283. aliasRoute,
  284. parent,
  285. record.path || '/' // matchAs
  286. )
  287. }
  288. }
  289. if (name) {
  290. if (!nameMap[name]) {
  291. nameMap[name] = record
  292. } else if (process.env.NODE_ENV !== 'production' && !matchAs) {
  293. warn(
  294. false,
  295. `Duplicate named routes definition: ` +
  296. `{ name: "${name}", path: "${record.path}" }`
  297. )
  298. }
  299. }
  300. }

在上文的构造函数中实例化的HTML5History对象就是对于history模式下的路由的处理,主要是通过继承History对象以及自身实现的方法完成路由。在初始化VueRouter时调用的init方法调用了路由切换以及调用了setupListeners方法实现了路由的切换的监听回调,注意此时并没有在HTML5History对象的构造函数中直接添加事件监听,这是因为需要避免在某些浏览器中调度第一个popstate事件,但是由于异步保护,第一个历史记录路由未同时更新的问题。history模式的代码结构以及更新视图的逻辑与hash模式基本类似,主要是监听popstate事件以及对于push()replace()方法的变动,使用History对象的pushState()replaceState()等方法进行路由的变换。

  1. // dev/src/index.js line 21
  2. export default class VueRouter {
  3. //...
  4. init (app: any /* Vue component instance */) {
  5. process.env.NODE_ENV !== 'production' &&
  6. assert(
  7. install.installed,
  8. `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
  9. `before creating root instance.`
  10. )
  11. this.apps.push(app)
  12. // set up app destroyed handler
  13. // https://github.com/vuejs/vue-router/issues/2639
  14. app.$once('hook:destroyed', () => {
  15. // clean out app from this.apps array once destroyed
  16. const index = this.apps.indexOf(app)
  17. if (index > -1) this.apps.splice(index, 1)
  18. // ensure we still have a main app or null if no apps
  19. // we do not release the router so it can be reused
  20. if (this.app === app) this.app = this.apps[0] || null
  21. if (!this.app) this.history.teardown()
  22. })
  23. // main app previously initialized
  24. // return as we don't need to set up new history listener
  25. if (this.app) {
  26. return
  27. }
  28. this.app = app
  29. const history = this.history
  30. if (history instanceof HTML5History || history instanceof HashHistory) {
  31. const handleInitialScroll = routeOrError => {
  32. const from = history.current
  33. const expectScroll = this.options.scrollBehavior
  34. const supportsScroll = supportsPushState && expectScroll
  35. if (supportsScroll && 'fullPath' in routeOrError) {
  36. handleScroll(this, routeOrError, from, false)
  37. }
  38. }
  39. const setupListeners = routeOrError => {
  40. history.setupListeners() // 初始化添加事件监听
  41. handleInitialScroll(routeOrError)
  42. }
  43. history.transitionTo( // 如果默认页,需要根据当前浏览器地址栏里的 path 或者 hash 来激活对应的路由
  44. history.getCurrentLocation(),
  45. setupListeners,
  46. setupListeners
  47. )
  48. }
  49. history.listen(route => {
  50. this.apps.forEach(app => {
  51. app._route = route
  52. })
  53. })
  54. }
  55. //...
  56. }
  57. // dev/src/history/base.js line 24
  58. export class History {
  59. // ...
  60. transitionTo (
  61. location: RawLocation,
  62. onComplete?: Function,
  63. onAbort?: Function
  64. ) {
  65. let route
  66. // catch redirect option https://github.com/vuejs/vue-router/issues/3201
  67. try {
  68. route = this.router.match(location, this.current) // // 获取匹配的路由信息
  69. } catch (e) {
  70. this.errorCbs.forEach(cb => {
  71. cb(e)
  72. })
  73. // Exception should still be thrown
  74. throw e
  75. }
  76. const prev = this.current
  77. this.confirmTransition( // 确认跳转
  78. route,
  79. () => {
  80. this.updateRoute(route) // 更新当前 route 对象
  81. onComplete && onComplete(route)
  82. this.ensureURL() // 子类实现的更新url地址 对于 hash 模式的话 就是更新 hash 的值
  83. this.router.afterHooks.forEach(hook => {
  84. hook && hook(route, prev)
  85. })
  86. // fire ready cbs once
  87. if (!this.ready) {
  88. this.ready = true
  89. this.readyCbs.forEach(cb => {
  90. cb(route)
  91. })
  92. }
  93. },
  94. err => {
  95. if (onAbort) {
  96. onAbort(err)
  97. }
  98. if (err && !this.ready) {
  99. // Initial redirection should not mark the history as ready yet
  100. // because it's triggered by the redirection instead
  101. // https://github.com/vuejs/vue-router/issues/3225
  102. // https://github.com/vuejs/vue-router/issues/3331
  103. if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
  104. this.ready = true
  105. this.readyErrorCbs.forEach(cb => {
  106. cb(err)
  107. })
  108. }
  109. }
  110. }
  111. )
  112. }
  113. confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
  114. const current = this.current
  115. this.pending = route
  116. const abort = err => {
  117. // changed after adding errors with
  118. // https://github.com/vuejs/vue-router/pull/3047 before that change,
  119. // redirect and aborted navigation would produce an err == null
  120. if (!isNavigationFailure(err) && isError(err)) {
  121. if (this.errorCbs.length) {
  122. this.errorCbs.forEach(cb => {
  123. cb(err)
  124. })
  125. } else {
  126. warn(false, 'uncaught error during route navigation:')
  127. console.error(err)
  128. }
  129. }
  130. onAbort && onAbort(err)
  131. }
  132. const lastRouteIndex = route.matched.length - 1
  133. const lastCurrentIndex = current.matched.length - 1
  134. if (
  135. isSameRoute(route, current) && // 如果是相同的路由就不跳转
  136. // in the case the route map has been dynamically appended to
  137. lastRouteIndex === lastCurrentIndex &&
  138. route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
  139. ) {
  140. this.ensureURL()
  141. return abort(createNavigationDuplicatedError(current, route))
  142. }
  143. const { updated, deactivated, activated } = resolveQueue( // 通过对比路由解析出可复用的组件,需要渲染的组件,失活的组件
  144. this.current.matched,
  145. route.matched
  146. )
  147. const queue: Array<?NavigationGuard> = [].concat( // 导航守卫数组
  148. // in-component leave guards
  149. extractLeaveGuards(deactivated), // 失活的组件钩子
  150. // global before hooks
  151. this.router.beforeHooks, // 全局 beforeEach 钩子
  152. // in-component update hooks
  153. extractUpdateHooks(updated), // 在当前路由改变,但是该组件被复用时调用
  154. // in-config enter guards
  155. activated.map(m => m.beforeEnter), // 需要渲染组件 enter 守卫钩子
  156. // async components
  157. resolveAsyncComponents(activated) // 解析异步路由组件
  158. )
  159. const iterator = (hook: NavigationGuard, next) => {
  160. if (this.pending !== route) { // 路由不相等就不跳转路由
  161. return abort(createNavigationCancelledError(current, route))
  162. }
  163. try {
  164. hook(route, current, (to: any) => { // 只有执行了钩子函数中的next,才会继续执行下一个钩子函数,否则会暂停跳转,以下逻辑是在判断 next() 中的传参
  165. if (to === false) {
  166. // next(false) -> abort navigation, ensure current URL
  167. this.ensureURL(true)
  168. abort(createNavigationAbortedError(current, route))
  169. } else if (isError(to)) {
  170. this.ensureURL(true)
  171. abort(to)
  172. } else if (
  173. typeof to === 'string' ||
  174. (typeof to === 'object' &&
  175. (typeof to.path === 'string' || typeof to.name === 'string'))
  176. ) {
  177. // next('/') or next({ path: '/' }) -> redirect
  178. abort(createNavigationRedirectedError(current, route))
  179. if (typeof to === 'object' && to.replace) {
  180. this.replace(to)
  181. } else {
  182. this.push(to)
  183. }
  184. } else {
  185. // confirm transition and pass on the value
  186. next(to)
  187. }
  188. })
  189. } catch (e) {
  190. abort(e)
  191. }
  192. }
  193. // ...
  194. }
  195. // ...
  196. }
  197. // dev/src/history/html5.js line 10
  198. export class HTML5History extends History {
  199. _startLocation: string
  200. constructor (router: Router, base: ?string) {
  201. super(router, base)
  202. this._startLocation = getLocation(this.base)
  203. }
  204. setupListeners () { // 初始化
  205. if (this.listeners.length > 0) {
  206. return
  207. }
  208. const router = this.router
  209. const expectScroll = router.options.scrollBehavior
  210. const supportsScroll = supportsPushState && expectScroll
  211. if (supportsScroll) {
  212. this.listeners.push(setupScroll())
  213. }
  214. const handleRoutingEvent = () => {
  215. const current = this.current
  216. // Avoiding first `popstate` event dispatched in some browsers but first
  217. // history route not updated since async guard at the same time.
  218. const location = getLocation(this.base)
  219. if (this.current === START && location === this._startLocation) {
  220. return
  221. }
  222. this.transitionTo(location, route => {
  223. if (supportsScroll) {
  224. handleScroll(router, route, current, true)
  225. }
  226. })
  227. }
  228. window.addEventListener('popstate', handleRoutingEvent) // 事件监听
  229. this.listeners.push(() => {
  230. window.removeEventListener('popstate', handleRoutingEvent)
  231. })
  232. }
  233. go (n: number) {
  234. window.history.go(n)
  235. }
  236. push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  237. const { current: fromRoute } = this
  238. this.transitionTo(location, route => {
  239. pushState(cleanPath(this.base + route.fullPath))
  240. handleScroll(this.router, route, fromRoute, false)
  241. onComplete && onComplete(route)
  242. }, onAbort)
  243. }
  244. replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  245. const { current: fromRoute } = this
  246. this.transitionTo(location, route => {
  247. replaceState(cleanPath(this.base + route.fullPath))
  248. handleScroll(this.router, route, fromRoute, false)
  249. onComplete && onComplete(route)
  250. }, onAbort)
  251. }
  252. ensureURL (push?: boolean) {
  253. if (getLocation(this.base) !== this.current.fullPath) {
  254. const current = cleanPath(this.base + this.current.fullPath)
  255. push ? pushState(current) : replaceState(current)
  256. }
  257. }
  258. getCurrentLocation (): string {
  259. return getLocation(this.base)
  260. }
  261. }

每日一题

  1. https://github.com/WindrunnerMax/EveryDay

参考

  1. https://www.jianshu.com/p/557f2ba86892
  2. https://juejin.im/post/6844904159678824456
  3. https://juejin.im/post/6844904012630720526
  4. https://juejin.im/post/6844904062698127367
  5. https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState
  6. https://liyucang-git.github.io/2019/08/15/vue-router%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
  7. https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90

Vue路由History模式分析的更多相关文章

  1. Vue路由Hash模式分析

    Vue路由Hash模式分析 Vue-router是Vue的核心组件,主要是作为Vue的路由管理器,Vue-router默认hash模式,即使用URL的Hash来模拟一个完整的URL,当URL改变时页面 ...

  2. K8s nginx-ingress 如何配置二级目录转发远程静态服务器基于Vue路由history模式打包的应用程序

    背景 首先这标题有点绕,我先解释下: 首先我们有静态服务器,上面某个目录有Vue路由history模式打包的应用程序(也就是build后的产物): 但是静态服务器一般不做对外域名用的,我们需要在k8s ...

  3. Vue路由history模式踩坑记录:nginx配置解决404问题

    问题背景: vue-router 默认是hash模式,使用url的hash来模拟一个完整的url,当url改变的时候,页面不会重新加载.但是如果我们不想hash这种以#号结尾的路径时候的话,我们可以使 ...

  4. vue路由history模式刷新页面出现404问题

    vue hash模式下,URL中存在'#',用'history'模式就能解决这个问题.但是history模式会出现刷新页面后,页面出现404.解决的办法是用nginx配置一下.在nginx的配置文件中 ...

  5. 新来的前端小姐姐问:Vue路由history模式刷新页面出现404问题

    摘要:vue-router 默认 hash 模式 -- 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载. 本文分享自华为云社区<学习Vue Rou ...

  6. 解决vue路由history模式刷新后404的问题

    server { listen ;#默认端口是80,如果端口没被占用可以不用修改 server_name localhost; root E:/vue/my_project/dist;#vue项目的打 ...

  7. VUE路由history模式坑记--NGINX

    因微信分享和自动登录需要,对于URL中存在'#'的地址,处理起来比较坑(需要手动写一些代码来处理).还有可能会有一些隐藏的问题没被发现. 如果VUE能像其他(JSP/PHP)系统的路径一样,就不存在这 ...

  8. vue路由history模式下打包node服务器配置

    vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载. 如果不想要很丑的 hash,我们可以用路由的 his ...

  9. vue路由history模式,nginx配置

    nginx配置内容 # For more information on configuration, see: # * Official English Documentation: http://n ...

随机推荐

  1. Linux常用命令--不断更新

    Linux命令: !. 1.[root@loc8lhost/root]# 表示登陆进去系统,其中#是超级⽤用户也即root⽤用 户的系统提示符 #. 2.reboot命令可以重启系统 $. 3.关闭系 ...

  2. js map对象处理if

    onButtonClick只有一个参数时候,map和object对象都可以 // onButtonClick1(3) onButtonClick只有一个参数时候,map和object对象都可以 con ...

  3. C016:字符串倒置

    代码: #include "stdafx.h" #include <string.h> int _tmain(int argc, _TCHAR* argv[]) { c ...

  4. 详解 LeetCode_007_整数反转(Java 实现)

    目录 LeetCode_007_整数反转 题目描述 总体分析 解决方案 小结 LeetCode_007_整数反转 题目描述 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示 ...

  5. asp.net中 使用Nginx 配置 IIS站点负载均衡

    这是一偏初学者入门的内容,发现有问题的地方,欢迎留言,一起学习,一起进步 本文主要记录一下在Windows平台中,IIS站点如何使用Nginx 做一个简单的负载均衡  一. 准备工作: 官网下载安装包 ...

  6. python基础一(安装、变量、循环、git)

    一.开发语言分类 系统的开发语言有java.c++.c#.python.ruby.php等等,开发语言可分为编译型语言和解释型语言. 编译型语言就是写好代码之后就把代码编译成二进制文件,运行的时候运行 ...

  7. Vue render函数 函数时组件 jsx

    常规组件使用 定义组件 components/list/list.vue <template> <ul> <li v-for="(item, index) in ...

  8. 一、Git是什么?

    Git是什么? Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控 ...

  9. 关于JAVA的一些零碎小知识

    1.经常遇到集合之间需要互相转化的 Array和List转化:Arrays.asList(数组):list.toArray(); List和Set转化:Set<String> set = ...

  10. Spring系列 之数据源的配置 数据库 数据源 连接池的区别

    Spring系列之数据源的配置 数据源,连接池,数据库三者的区别 连接池:这个应该都学习过,比如c3p0,druid等等,连接池的作用是为了提高程序的效率,因为频繁的去创建,关闭数据库连接,会对性能有 ...