大家好,欢迎来到今天的“Vue Router 源码探秘”讲座。今天咱们不讲高深莫测的理论,直接扒开 Vue Router 的底裤,看看 createWebHistory 和 createWebHashHistory 这两个家伙到底是怎么监听 URL 变化的。
咱们先来简单回顾一下,Vue Router 提供了几种不同的 history 模式,它们决定了你的应用 URL 的外观和行为。 其中最常见的两种就是今天的主角:
-
Web History (HTML5 History API): 使用
createWebHistory创建,URL 看起来像正常的网站,例如/about或/users/123。它利用了浏览器提供的history.pushState和history.replaceState方法,以及popstate事件。 -
Hash History: 使用
createWebHashHistory创建,URL 中会带有一个#符号,例如/#/about或/#/users/123。 它的工作原理是监听 URL 中#后面的内容变化,并且通过window.location.hash来读取和设置 hash 值。
那么,它们是如何具体实现的呢? 让我们开始深入源码!
一、createWebHistory 的实现细节
createWebHistory 的核心思想是利用 HTML5 History API 提供的能力,优雅地操作浏览器的历史记录,并且监听 popstate 事件来感知用户的前进后退操作。
-
createWebHistory函数首先,我们来看一下
createWebHistory函数本身。 它的作用是创建一个 history 对象,这个对象会提供一些方法来操作 URL,并且监听 URL 的变化。// 简化后的 createWebHistory 函数 (源码位置: packages/router/src/history/html5.ts) function createWebHistory(base = '') { // 处理 base URL base = normalizeBase(base); // history 栈的初始状态 const history = { location: START, // 初始 URL state: null, // 初始 state base, push(to, data) { // ... pushState 的逻辑 }, replace(to, data) { // ... replaceState 的逻辑 }, listen(callback) { // ... 监听 popstate 事件 }, destroy() { // ... 移除监听器 }, }; return history; } // normalizeBase 函数,用于处理 base URL 的逻辑 function normalizeBase(base) { if (!base) { return ''; } if (base.startsWith('/')) { return base; } if (base.startsWith('.')) { // 处理相对路径 return (new URL(base, document.location.origin)).pathname; } // 如果不是 '/' 或 '.' 开头,则认为是绝对路径 return '/' + base; }这个
createWebHistory函数返回一个对象,这个对象包含一些方法,比如push、replace和listen。 这些方法会被 Vue Router 内部调用,用来更新 URL 和监听 URL 的变化。normalizeBase函数是为了确保 base URL 的格式正确。 -
push和replace方法push方法用于向历史记录中添加一个新的条目,replace方法用于替换当前的条目。 它们都利用了history.pushState和history.replaceState方法。// 简化后的 push 和 replace 方法 push(to, data) { const url = resolveURL(to, history.base); // 解析 URL history.state = data; history.location = url; window.history.pushState(data, '', url); }, replace(to, data) { const url = resolveURL(to, history.base); // 解析 URL history.state = data; history.location = url; window.history.replaceState(data, '', url); }, // resolveURL 函数,用于解析 URL function resolveURL(to, base) { if (to.startsWith('/')) { return base + to; } if (to.startsWith('.')) { return (new URL(to, document.location.origin + base)).pathname; } // 如果不是 '/' 或 '.' 开头,则认为是绝对路径 return to; }这两个方法都接收一个
to参数,表示要跳转到的 URL。 它们还会接收一个可选的data参数,这个参数可以用来存储一些状态信息,这些信息会和历史记录一起保存。resolveURL函数用来将传入的to参数解析成完整的 URL。 -
listen方法listen方法用于监听popstate事件。 当用户点击浏览器的前进或后退按钮时,会触发popstate事件。// 简化后的 listen 方法 listen(callback) { const popStateHandler = () => { history.location = window.location.pathname; // 获取当前 URL callback(history.location, history.state); // 调用回调函数 }; window.addEventListener('popstate', popStateHandler); return () => { window.removeEventListener('popstate', popStateHandler); // 返回一个函数,用于移除监听器 }; },listen方法接收一个回调函数,当popstate事件触发时,会调用这个回调函数,并且传入当前 URL 和 state。 它返回一个函数,调用这个函数可以移除监听器。注意:
popstate事件只会在用户通过浏览器的前进后退按钮或者调用history.back()、history.forward()、history.go()方法时触发。 通过history.pushState()或者history.replaceState()方法改变 URL 不会触发popstate事件。 这也是为什么 Vue Router 需要手动更新内部状态的原因。 -
总结
createWebHistory的实现原理可以总结为以下几点:- 使用
history.pushState和history.replaceState方法来操作 URL。 - 监听
popstate事件来感知用户的导航操作。 - 手动更新内部状态,因为
pushState和replaceState方法不会触发popstate事件。
- 使用
二、createWebHashHistory 的实现细节
createWebHashHistory 的实现相对简单,因为它只需要操作 URL 中的 hash 部分。
-
createWebHashHistory函数// 简化后的 createWebHashHistory 函数 (源码位置: packages/router/src/history/hash.ts) function createWebHashHistory(base = '') { // 处理 base URL base = normalizeBase(base); const history = { location: getHash(), // 初始 URL state: null, // 初始 state base, push(to, data) { // ... 设置 hash 的逻辑 }, replace(to, data) { // ... 替换 hash 的逻辑 }, listen(callback) { // ... 监听 hashchange 事件 }, destroy() { // ... 移除监听器 }, }; return history; } // normalizeBase 函数,用于处理 base URL 的逻辑 function normalizeBase(base) { if (!base) { return ''; } if (base.startsWith('/')) { return base; } if (base.startsWith('.')) { // 处理相对路径 return (new URL(base, document.location.origin)).pathname; } // 如果不是 '/' 或 '.' 开头,则认为是绝对路径 return '/' + base; } // getHash 函数,用于获取 hash 值 function getHash() { let href = window.location.href; const index = href.indexOf('#'); if (index < 0) return ''; href = href.slice(index + 1); return href; }createWebHashHistory函数返回一个 history 对象,这个对象包含一些方法,比如push、replace和listen。 这些方法会被 Vue Router 内部调用,用来更新 URL 和监听 URL 的变化。getHash函数用于获取当前 URL 中的 hash 值。 -
push和replace方法push和replace方法用于设置 URL 中的 hash 值。// 简化后的 push 和 replace 方法 push(to, data) { setHash(to); history.location = to; history.state = data; }, replace(to, data) { setHash(to); history.location = to; history.state = data; }, // setHash 函数,用于设置 hash 值 function setHash(to) { window.location.hash = to; }这两个方法都接收一个
to参数,表示要设置的 hash 值。 它们会直接修改window.location.hash属性。 -
listen方法listen方法用于监听hashchange事件。 当 URL 中的 hash 值发生变化时,会触发hashchange事件。// 简化后的 listen 方法 listen(callback) { const hashChangeHandler = () => { history.location = getHash(); // 获取当前 hash 值 callback(history.location, history.state); // 调用回调函数 }; window.addEventListener('hashchange', hashChangeHandler); return () => { window.removeEventListener('hashchange', hashChangeHandler); // 返回一个函数,用于移除监听器 }; },listen方法接收一个回调函数,当hashchange事件触发时,会调用这个回调函数,并且传入当前 hash 值和 state。 它返回一个函数,调用这个函数可以移除监听器。 -
总结
createWebHashHistory的实现原理可以总结为以下几点:- 使用
window.location.hash属性来操作 URL 中的 hash 值。 - 监听
hashchange事件来感知 URL 的变化。
- 使用
三、两种 History 模式的比较
为了更清晰地理解 createWebHistory 和 createWebHashHistory 的区别,我们用一个表格来对比一下它们:
| 特性 | createWebHistory (HTML5 History API) |
createWebHashHistory (Hash History) |
|---|---|---|
| URL 外观 | /about, /users/123 |
/#/about, /#/users/123 |
| 依赖 | 浏览器支持 HTML5 History API | 浏览器基本支持 |
| SEO 友好性 | 更好 | 较差 |
| 服务器配置 | 需要服务器配置,确保所有路由都指向 index.html | 不需要服务器配置 |
| 监听事件 | popstate |
hashchange |
| 实现复杂度 | 较高 | 较低 |
| 初次加载 | URL 正常显示 | URL 中包含 # |
四、URL 变化的监听机制深度剖析
现在,我们来深入探讨一下这两种 history 模式是如何监听 URL 变化的。
1. createWebHistory 的 popstate 事件监听
createWebHistory 依赖浏览器的 popstate 事件。这个事件在以下情况下会被触发:
- 用户点击浏览器的前进或后退按钮。
- JavaScript 代码调用
history.back()、history.forward()或history.go()方法。
当 popstate 事件触发时,createWebHistory 会:
- 读取
window.location.pathname获取当前的 URL。 - 调用注册的回调函数,通知 Vue Router URL 已经改变。
重要提示: history.pushState() 和 history.replaceState() 方法虽然可以改变 URL,但它们不会触发 popstate 事件! 这就是为什么 Vue Router 在调用这两个方法之后,需要手动更新内部状态的原因。
为什么 pushState 和 replaceState 不触发 popstate?
这是 HTML5 History API 的设计决定的。 如果每次调用 pushState 或 replaceState 都触发 popstate 事件,那么会导致无限循环,因为 popstate 事件的处理函数又会调用 pushState 或 replaceState。
2. createWebHashHistory 的 hashchange 事件监听
createWebHashHistory 依赖浏览器的 hashchange 事件。这个事件在以下情况下会被触发:
- URL 中的 hash 值发生变化。
- JavaScript 代码直接修改
window.location.hash属性。
当 hashchange 事件触发时,createWebHashHistory 会:
- 读取
window.location.hash获取当前的 hash 值。 - 调用注册的回调函数,通知 Vue Router URL 已经改变。
总结:
createWebHistory通过监听popstate事件来感知浏览器的前进后退操作,并且手动更新内部状态。createWebHashHistory通过监听hashchange事件来感知 URL 中 hash 值的变化。
五、 实际应用场景分析
让我们通过一些实际场景来分析这两种 History 模式的选择:
-
场景 1: 需要良好的 SEO
如果你的应用需要被搜索引擎抓取,那么
createWebHistory是更好的选择。 因为搜索引擎可以更容易地理解和索引没有#符号的 URL。 -
场景 2: 兼容旧浏览器
如果你的应用需要兼容一些比较老的浏览器,而这些浏览器可能不支持 HTML5 History API,那么
createWebHashHistory是一个更安全的选择。 因为所有浏览器都支持hashchange事件。 -
场景 3: 简单的静态网站
如果你的应用是一个简单的静态网站,不需要服务器端的特殊配置,那么
createWebHashHistory可以让你快速部署。 -
场景 4: 需要控制应用的 base URL
两种模式都支持设置
baseURL,但是createWebHistory更加灵活,因为它可以处理更复杂的 base URL 结构。
六、 源码调试技巧
如果你想更深入地了解 Vue Router 的源码,可以尝试以下调试技巧:
- 下载 Vue Router 的源码: 从 GitHub 上下载 Vue Router 的源码。
- 使用
console.log语句: 在关键的代码位置插入console.log语句,打印一些变量的值,以便了解代码的执行流程。 - 使用断点调试: 在浏览器或者 IDE 中设置断点,然后运行你的 Vue 应用,当代码执行到断点时,你可以查看当前的变量值和调用栈。
- 阅读测试用例: Vue Router 的测试用例可以帮助你理解代码的功能和用法。
七、 总结
今天我们深入探讨了 Vue Router 源码中 createWebHistory 和 createWebHashHistory 的实现细节,以及它们是如何监听 URL 变化的。希望通过今天的讲解,你能够对 Vue Router 的内部机制有更深入的了解。
记住,理解源码不是为了死记硬背,而是为了更好地理解框架的设计思想,从而能够更灵活地使用框架,并且在遇到问题时能够更快地找到解决方案。
今天的讲座就到这里,谢谢大家!