深入理解 Vue Router 源码中 `createWebHistory` 和 `createWebHashHistory` 的实现细节,以及它们如何监听 URL 变化。

各位观众老爷,大家好!今天咱们来聊聊 Vue Router 里的两位老朋友:createWebHistorycreateWebHashHistory。别看名字长,其实它们就是 Vue 应用里 URL 的管家,负责管理你的页面地址,让你在不同页面间跳来跳去,体验丝滑顺畅。

咱们不搞虚头巴脑的,直接深入源码,看看这两位管家到底是怎么干活的。

一、 createWebHistory: 优雅的绅士

createWebHistory,顾名思义,创造一个基于 Web History API 的路由历史。 这位爷追求优雅,它使用浏览器的 history.pushStatehistory.replaceState 方法来修改 URL,而且不会引起页面刷新。 这样,你的 URL 看上去就像正常的网站地址一样,比如 https://example.com/users/123

1. 源码结构

先来看看 createWebHistory 函数的大致结构(简化版,去掉了类型定义和一些边界情况处理):

function createWebHistory(base) {
  if ( base === void 0 ) base = '';

  const history = window.history;
  //确保base以'/'开头和结尾
  const baseRef = ref(base);
  const current = ref(getLocation(baseRef.value)); //当前路由

  function push(to, data) {
    const url = resolve(to); //解析to成完整的url
    history.pushState(data, '', url);
    current.value = getLocation(baseRef.value); //更新当前路由
  }

  function replace(to, data) {
    const url = resolve(to);
    history.replaceState(data, '', url);
    current.value = getLocation(baseRef.value);
  }

  function listen(callback) {
    const popStateHandler = () => {
      current.value = getLocation(baseRef.value);
      callback({
        type: NavigationType.pop,
        state: history.state
      });
    };
    window.addEventListener('popstate', popStateHandler);
    return () => {
      window.removeEventListener('popstate', popStateHandler);
    };
  }

  function getLocation(base) {
    let path = window.location.pathname;
    if (base && path.startsWith(base)) {
      path = path.slice(base.length);
    }
    return path + window.location.search + window.location.hash;
  }

  function resolve(to) {
      if (typeof to === 'string') {
          return baseRef.value + to
      }
      // 省略处理对象形式的to,比如 { path: '/users/123', query: { ... } }
      return baseRef.value + to.path
  }
  return {
    location: current,
    base: baseRef,
    push,
    replace,
    listen
  };
}

2. 核心功能解析

  • getLocation(base): 这个函数从 window.location 获取当前 URL,并移除 base 部分(如果存在)。 base 通常是你的应用部署的根路径,例如 /app/。 这个函数确保返回的路径是相对于你的应用的。

    • 举个例子:如果 window.location.pathname/app/users/123,且 base/app/,那么 getLocation 会返回 /users/123
  • push(to, data): 这个函数使用 history.pushState 来创建一个新的历史记录。 to 是目标 URL,data 是与该历史记录关联的数据(可以是任何 JavaScript 对象,通常用于存储路由状态)。

    • 关键点:pushState 不会引起页面刷新,但会在浏览器的历史记录中添加一条新的记录。
  • replace(to, data): 与 push 类似,但使用 history.replaceState 来替换当前的浏览历史记录,而不是添加新的记录。
    • 关键点:replaceState 同样不会引起页面刷新,但会用新的 URL 和数据覆盖当前的历史记录。
  • listen(callback): 这个函数监听 popstate 事件。 当用户点击浏览器的前进/后退按钮时,会触发 popstate 事件。 listen 函数会调用 callback,并传递一个包含导航类型(pop)和状态(history.state)的对象。

    • 关键点:popstate 事件是 createWebHistory 监听 URL 变化的关键。 浏览器会自动处理前进/后退操作,popstate 事件会通知你的应用 URL 已经改变。

3. 工作流程

  1. 初始化: createWebHistory 函数会被调用,传入一个 base 参数(可选)。
  2. 首次加载: getLocation 函数获取当前 URL,并将其设置为 current 响应式变量的值。
  3. 导航:
    • 当你的应用调用 router.push(to)router.replace(to) 时,pushreplace 函数会被调用。
    • pushreplace 函数会调用 history.pushStatehistory.replaceState 来更新 URL,并更新 current 响应式变量的值。
  4. 后退/前进:
    • 当用户点击浏览器的前进/后退按钮时,会触发 popstate 事件。
    • listen 函数监听 popstate 事件,并调用回调函数。
    • 回调函数会更新 current 响应式变量的值,从而触发 Vue 组件的重新渲染。

二、 createWebHashHistory: 朴实的农民

createWebHashHistory 比较实在,它使用 URL 的 hash 部分(# 符号后面的内容)来模拟路由。 它的 URL 看起来像这样:https://example.com/#/users/123

1. 源码结构

function createWebHashHistory(base) {
  if ( base === void 0 ) base = '';

  const history = window.history;

  const baseRef = ref(base);
  const current = ref(getHash());

  function push(to, data) {
    const url = resolve(to);
    writeHash(url);
    current.value = getHash();
  }

  function replace(to, data) {
    const url = resolve(to);
    history.replaceState(data, '', url); //这里需要注意,replaceState只是修改了整个url,hash部分不变
    current.value = getHash();
  }

  function listen(callback) {
    const hashChangeHandler = () => {
      current.value = getHash();
      callback({
        type: NavigationType.hashchange,
        state: history.state
      });
    };
    window.addEventListener('hashchange', hashChangeHandler);

    return () => {
      window.removeEventListener('hashchange', hashChangeHandler);
    };
  }
  function resolve(to) {
      if (typeof to === 'string') {
          return baseRef.value + '#' + to
      }
      return baseRef.value + '#' + to.path
  }

  function getHash() {
    // We can't use location.hash here because it's not
    // consistent across browsers - Firefox decodes before
    // returning the value.
    let href = window.location.href;
    const index = href.indexOf('#');
    // Empty string if no hash, otherwise the entire hash
    return index < 0 ? '' : href.slice(index);
  }

  function writeHash(path) {
    window.location.hash = path;
  }

  return {
    location: current,
    base: baseRef,
    push,
    replace,
    listen
  };
}

2. 核心功能解析

  • getHash(): 这个函数从 window.location.href 获取 URL 的 hash 部分。 注意,它不直接使用 window.location.hash,因为不同浏览器对 window.location.hash 的处理方式可能不一致。
  • writeHash(path): 这个函数设置 window.location.hash 的值。 这会改变 URL 的 hash 部分,但不会引起页面刷新。
  • push(to, data): 这个函数调用 writeHash 来更新 URL 的 hash 部分,并更新 current 响应式变量的值。 data 参数在这里其实没有被使用,因为 hash 模式下,history.state 不太好管理,所以 Vue Router 简化了这个逻辑。
  • replace(to, data): 这个函数调用 history.replaceState 修改整个url,但hash部分是不变的,同时更新 current 响应式变量的值。 data 参数在这里其实没有被使用。
  • listen(callback): 这个函数监听 hashchange 事件。 当 URL 的 hash 部分发生改变时,会触发 hashchange 事件。 listen 函数会调用 callback,并传递一个包含导航类型(hashchange)和状态(history.state)的对象。

    • 关键点:hashchange 事件是 createWebHashHistory 监听 URL 变化的关键。 浏览器会自动处理 hash 的改变,hashchange 事件会通知你的应用 URL 已经改变。

3. 工作流程

  1. 初始化: createWebHashHistory 函数会被调用,传入一个 base 参数(可选)。
  2. 首次加载: getHash 函数获取当前 URL 的 hash 部分,并将其设置为 current 响应式变量的值。
  3. 导航:
    • 当你的应用调用 router.push(to)router.replace(to) 时,pushreplace 函数会被调用。
    • push 函数会调用 writeHash 来更新 URL 的 hash 部分,并更新 current 响应式变量的值。
    • replace 函数调用 history.replaceState 修改整个url,但hash部分是不变的,同时更新 current 响应式变量的值。
  4. 后退/前进:
    • 当用户点击浏览器的前进/后退按钮时,如果 hash 发生了改变,会触发 hashchange 事件。
    • listen 函数监听 hashchange 事件,并调用回调函数。
    • 回调函数会更新 current 响应式变量的值,从而触发 Vue 组件的重新渲染。

三、 差异对比

为了更清楚地理解两者的区别,我们用表格来总结一下:

特性 createWebHistory createWebHashHistory
URL 格式 https://example.com/users/123 https://example.com/#/users/123
使用 API history.pushState, history.replaceState, popstate window.location.hash, hashchange, history.replaceState
SEO 友好度 更好,更符合传统网站的 URL 结构 较差,搜索引擎可能忽略 hash 部分
服务器配置 需要服务器配置,以确保所有路径都指向你的应用 不需要服务器配置,因为 hash 部分不会发送到服务器
兼容性 现代浏览器 几乎所有浏览器
核心监听事件 popstate hashchange

四、 选择哪一个?

  • createWebHistory: 如果你的应用需要更好的 SEO,并且你能够配置你的服务器,那么 createWebHistory 是一个更好的选择。 它的 URL 结构更清晰,更符合传统网站的习惯。
  • createWebHashHistory: 如果你的应用不需要 SEO,或者你无法配置你的服务器(例如,你的应用部署在 GitHub Pages 上),那么 createWebHashHistory 是一个更简单、更兼容的选择。

五、 总结

createWebHistorycreateWebHashHistory 都是 Vue Router 中用于管理 URL 的工具。 createWebHistory 使用 Web History API,提供更优雅的 URL 结构,但需要服务器配置。 createWebHashHistory 使用 URL 的 hash 部分,不需要服务器配置,但 SEO 较差。

理解了它们的实现细节,你就能更好地选择适合你的应用的路由模式,并能更深入地理解 Vue Router 的工作原理。

希望今天的讲座对大家有所帮助! 如果有什么疑问,欢迎随时提问。 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注