Vue-Router3源码拾遗【5】改变url

上一篇文章已经介绍了核心模块。这篇文章介绍外围的提供给用户的 api 是怎么实现的,还有如何将变化同步到浏览器 url 的。

对于外围 api,会考虑兼容降级调用浏览器提供的 BOM api,所以先要对这部分知识有一个基本的理解,可以参考本系列文章的第一篇。

这部分代码是真的难读,某某某.push某某某.history满屏幕都是,字段名又是命名得类似而且一词用于多义,有时候都分不清谁是谁了。再者考虑了兼容性问题,就算是搜出来的字段,经常不知道要点进哪个文件去看。

特别提醒:HTML5 history的 pushState 和 replaceState,可以设置history.state并改变 url 地址,但不会刷新页面。其被提出的目的,就是想像 hash History 一样,能实现类似单页应用的效果并且局部刷新页面。

router.push

当我们点击 router-link 的时候,或者直接函数式调用this.$router.push,实际上最终会执行 router.push,如下:

push (location, onComplete, onAbort) {
  this.history.push(location, onComplete, onAbort)
}

this.history.push 函数,这个函数是子类实现的,不同模式下该函数的实现略有不同,我们来看一下平时使用比较多的 hash 模式该函数的实现,src/history/hash.js 中:

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  const { current: fromRoute } = this
  this.transitionTo(location, route => {
    pushHash(route.fullPath)
    // handleScroll(this.router, route, fromRoute, false)
    onComplete && onComplete(route)
  }, onAbort)
}

history.replaceState 和 history.pushState 的区别的表现,就是 浏览器回退的时候,回退到上一条记录去哪

其实主干逻辑很纯粹,复杂的代码就是在遇到需要兼容或者降级的时候区分执行的逻辑就行了。

push 函数会先执行 this.transitionTo 做路径切换,在其的回调函数中,执行 pushHash 函数:

  • (hash 模式下的)push
    • this.transitionTo 做路径切换 (可能会调用 onComplete 或 onAbort。它们就类似于Promise的 resolve和 reject 回调 )
    • 在回调中执行 pushHash (还有执行并列的handleScroll)。
      • (supportsPushState如果支持这个api则返回。 (其实是对ua的判断,也就是 window.navigator.userAgent))
      • 【判断浏览器如果支持 pushState】则获取当前完整的 url,执行 pushState 这个包装方法
        • 调用浏览器原生的 historypushState 接口或者 replaceState 接口,更新浏览器的 url 地址
      • 【判断浏览器如果不支持 pushState】 则直接替换:window.location.hash = path
    • (若有则)调用 onComplete

点击浏览器前进或后退按钮的响应,即监听历史栈的变化

history 的初始化中,会设置一个监听器,监听历史栈的变化:

setupListeners () {
  const router = this.router
  // scroll相关....
  window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {
    if (!ensureSlash()) return;
    this.transitionTo(getHash(), route => {
      // scroll相关....
      if (!supportsPushState) {
        replaceHash(route.fullPath)
      }
    })
  })
}

当点击浏览器前进或后退按钮的时候,如果已经有 url 被压入历史栈,则会触发 popstate 事件,然后拿到当前要跳转的 hash,执行 transtionTo 方法做一次路径转换。

ensureSlash :

同学们在使用 Vue-Router 开发项目的时候,打开调试页面 http://localhost:8080 后会自动把 url 修改为 http://localhost:8080/#/,这是怎么做到呢?原来在实例化 HashHistory 的时候,构造函数会执行 ensureSlash() 方法:

原理:基于兼容包装replaceHash函数,也就是 history.replaceState 或者 window.location.replace

原理和实现的代码其实都很简单,就是正如开篇所说的兼容、函数套娃等原因,搞得很难读懂。

function ensureSlash (): boolean {
  const path = getHash()
  if (path.charAt(0) === '/') {
    return true
  }
  replaceHash('/' + path)
  return false
}

export function getHash (): string {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  const href = window.location.href
  const index = href.indexOf('#')
  return index === -1 ? '' : href.slice(index + 1)
}

function getUrl (path) {
  const href = window.location.href
  const i = href.indexOf('#')
  const base = i >= 0 ? href.slice(0, i) : href
  return `${base}#${path}`
}

function replaceHash (path) {
  if (supportsPushState) {
    replaceState(getUrl(path))
  } else {
    window.location.replace(getUrl(path))
  }
}

export function replaceState (url?: string) {
  pushState(url, true)
}

总结:

还是利用浏览器的api:

  • (不严谨统计)
  • history.replaceState
  • history.pushState
  • window.location.replace
  • window.location.assign
  • window.addEventListener(‘popstate’, function () {})
  • window.addEventListener(‘hashchange’, function () {})
文章目录
  1. router.push
  2. 点击浏览器前进或后退按钮的响应,即监听历史栈的变化
    1. ensureSlash :
  • 总结: