大家好,欢迎来到今天的“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
两种模式都支持设置
base
URL,但是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 的内部机制有更深入的了解。
记住,理解源码不是为了死记硬背,而是为了更好地理解框架的设计思想,从而能够更灵活地使用框架,并且在遇到问题时能够更快地找到解决方案。
今天的讲座就到这里,谢谢大家!