各位观众老爷,晚上好!我是老码农,今天咱们来聊聊Vue Router里两个亲兄弟:createWebHistory
和 createWebHashHistory
,看看它们是怎么监视你浏览器地址栏的小秘密的。准备好了吗?发车咯!
开场白:路由,前端的导航员
想象一下,你打开一个网站,点击不同的链接,页面内容随之改变,但页面并没有完全刷新。这就是路由在默默工作。它就像一个导航员,指引着你从一个“页面”到另一个“页面”,而这些“页面”其实是组件在不同状态下的呈现。
Vue Router 就是 Vue.js 官方提供的路由管理器,它让构建单页应用(SPA)变得简单。而 createWebHistory
和 createWebHashHistory
则是两种不同的历史模式,它们决定了你的 URL 看起来是什么样子,以及如何与服务器交互。
第一幕:历史模式的选择,一场关于 URL 的审美之争
在开始深入源码之前,我们先来明确一下 createWebHistory
和 createWebHashHistory
的区别。
特性 | createWebHistory (HTML5 History Mode) |
createWebHashHistory (Hash Mode) |
---|---|---|
URL 形式 | /about |
/#/about |
SEO 友好度 | 更好 | 较差 |
服务器配置 | 需要 | 不需要 |
兼容性 | 现代浏览器 | 较好 |
简单来说,createWebHistory
的 URL 更干净,更符合传统网站的形式,但需要服务器配置来正确处理所有路由。而 createWebHashHistory
的 URL 带有 #
,不需要服务器配置,兼容性更好,但 SEO 不友好。
第二幕:createWebHistory
,优雅的舞者
createWebHistory
基于 HTML5 History API,它提供了 pushState
、replaceState
和 popstate
事件,让我们可以修改 URL,而无需重新加载页面。
让我们先来看看 createWebHistory
的大致结构(以下代码为简化版本,仅用于说明原理):
function createWebHistory(base) {
if (base === void 0) { base = ''; } // 默认 base 为空
// 确保 base 以 '/' 开头和结尾
base = ensureStartingSlash(base);
base = normalizeBase(base);
const history = {
location: getCurrentLocation(base),
state: history.state,
push: (to, data) => {
const url = base + to;
window.history.pushState(data, '', url);
history.location = getCurrentLocation(base); // 更新 location
history.state = data;
// 触发路由变化
triggerListeners(history.location, history.state);
},
replace: (to, data) => {
const url = base + to;
window.history.replaceState(data, '', url);
history.location = getCurrentLocation(base); // 更新 location
history.state = data;
// 触发路由变化
triggerListeners(history.location, history.state);
},
listen: (callback) => {
listeners.push(callback);
return () => {
listeners = listeners.filter(l => l !== callback);
};
},
go: (delta) => {
window.history.go(delta);
},
// ... 其他方法
};
// 监听 popstate 事件
window.addEventListener('popstate', () => {
history.location = getCurrentLocation(base);
history.state = history.state; // history.state 不会因为popstate改变
triggerListeners(history.location, history.state);
});
return history;
}
// 辅助函数,用于获取当前 location
function getCurrentLocation(base) {
const raw = base ? window.location.pathname.slice(base.length) : window.location.pathname;
return raw || '/';
}
// 辅助函数,用于触发监听器
let listeners = [];
function triggerListeners(location, state) {
listeners.forEach(listener => listener(location, state));
}
function ensureStartingSlash(path) {
return path.startsWith('/') ? path : '/' + path;
}
function normalizeBase(base) {
if (!base.endsWith('/')) {
base += '/'
}
return base;
}
让我们逐行解读这段代码:
-
createWebHistory(base)
: 接收一个base
参数,用于指定应用的基础路径。例如,如果你的应用部署在https://example.com/app/
,那么base
就应该是/app/
。 -
ensureStartingSlash(base)
和normalizeBase(base)
: 确保base
路径以/
开头和结尾,这是为了方便后续的 URL 处理。 -
history
对象: 这是createWebHistory
返回的核心对象,它包含了以下关键方法:location
: 当前的 URL 路径。state
: 与当前历史记录项关联的状态数据。push(to, data)
: 向历史记录中添加一个新的条目,to
是要跳转的路径,data
是可选的状态数据。它会调用window.history.pushState
来修改 URL,并触发路由变化。replace(to, data)
: 替换当前历史记录项,类似于push
,但不会在历史记录中创建新的条目。listen(callback)
: 注册一个回调函数,当路由发生变化时会被调用。这个回调函数接收新的location
和state
作为参数。go(delta)
: 在历史记录中前进或后退,delta
是一个整数,表示要前进或后退的步数。
-
window.addEventListener('popstate', ...)
: 监听popstate
事件,当用户点击浏览器的前进/后退按钮时,或者调用history.go()
方法时,会触发这个事件。在事件处理函数中,我们会更新history.location
和history.state
,并触发路由变化。注意,popstate
事件只会在浏览器历史记录发生变化时触发,而不会在调用pushState
或replaceState
时触发。 -
getCurrentLocation(base)
: 根据当前的window.location.pathname
和base
路径,计算出当前的 URL 路径。 -
triggerListeners(location, state)
: 遍历所有注册的监听器,并调用它们,将新的location
和state
传递给它们。
关键点:pushState
、replaceState
和 popstate
pushState(state, title, url)
: 向历史记录中添加一个新的条目。state
是与这个条目关联的数据,title
是页面的标题(现在大多数浏览器会忽略这个参数),url
是新的 URL。replaceState(state, title, url)
: 替换当前历史记录条目。popstate
事件: 当用户点击浏览器的前进/后退按钮,或者调用history.go()
方法时,会触发这个事件。通过监听这个事件,我们可以知道用户在历史记录中进行了导航。
第三幕:createWebHashHistory
,勤劳的搬运工
createWebHashHistory
使用 URL 的 hash 部分(#
及其后面的内容)来模拟路由。它不需要服务器配置,因为浏览器不会将 hash 部分发送到服务器。
以下是 createWebHashHistory
的简化版本:
function createWebHashHistory(base) {
if (base === void 0) { base = ''; }
base = ensureStartingSlash(base);
base = normalizeBase(base);
const history = {
location: getHash(),
state: {},
push: (to, data) => {
setHash(base + to);
history.location = getHash();
history.state = data;
triggerListeners(history.location, history.state);
},
replace: (to, data) => {
replaceHash(base + to);
history.location = getHash();
history.state = data;
triggerListeners(history.location, history.state);
},
listen: (callback) => {
listeners.push(callback);
return () => {
listeners = listeners.filter(l => l !== callback);
};
},
go: (delta) => {
window.history.go(delta); // hash 模式也需要 go,因为浏览器的前进后退需要能触发hashchange
},
// ... 其他方法
};
// 监听 hashchange 事件
window.addEventListener('hashchange', () => {
history.location = getHash();
triggerListeners(history.location, history.state);
});
return history;
function getHash() {
let href = window.location.href;
const index = href.indexOf('#');
if (index < 0) return '/';
href = href.slice(index + 1);
return href.startsWith('/') ? href : '/' + href;
}
function setHash(path) {
window.location.hash = path;
}
function replaceHash(path) {
const href = window.location.href;
const index = href.indexOf('#');
window.location.replace(href.slice(0, index >= 0 ? index : href.length) + '#' + path);
}
// 辅助函数,用于触发监听器
let listeners = [];
function triggerListeners(location, state) {
listeners.forEach(listener => listener(location, state));
}
function ensureStartingSlash(path) {
return path.startsWith('/') ? path : '/' + path;
}
function normalizeBase(base) {
if (!base.endsWith('/')) {
base += '/'
}
return base;
}
}
这段代码与 createWebHistory
类似,但有以下关键区别:
-
getHash()
: 从window.location.href
中提取 hash 部分,作为当前的 URL 路径。 -
setHash(path)
: 设置window.location.hash
,从而改变 URL 的 hash 部分。 -
replaceHash(path)
: 替换当前的 hash 值,避免在历史记录中产生新的条目。 -
window.addEventListener('hashchange', ...)
: 监听hashchange
事件,当 URL 的 hash 部分发生变化时,会触发这个事件。在事件处理函数中,我们会更新history.location
,并触发路由变化。
关键点:hashchange
事件
hashchange
事件: 当 URL 的 hash 部分发生变化时,会触发这个事件。通过监听这个事件,我们可以知道用户在 hash 路由中进行了导航。
第四幕:监听 URL 变化的秘密武器
无论是 createWebHistory
还是 createWebHashHistory
,它们的核心都在于监听 URL 的变化,并通知 Vue Router 进行相应的处理。
createWebHistory
: 通过监听popstate
事件来感知 URL 的变化。createWebHashHistory
: 通过监听hashchange
事件来感知 URL 的变化。
当 URL 发生变化时,它们会:
- 更新
history.location
和history.state
。 - 调用
triggerListeners
函数,通知所有注册的监听器。
Vue Router 会在这些监听器中更新组件的渲染,从而实现页面的切换。
第五幕:总结与思考
createWebHistory
和 createWebHashHistory
是 Vue Router 中两种不同的历史模式,它们分别基于 HTML5 History API 和 URL 的 hash 部分来实现路由功能。
createWebHistory
的 URL 更干净,但需要服务器配置。createWebHashHistory
不需要服务器配置,兼容性更好,但 URL 带有#
,SEO 不友好。
它们都通过监听特定的事件(popstate
或 hashchange
)来感知 URL 的变化,并通知 Vue Router 进行相应的处理。
理解了它们的实现原理,你就能更好地选择合适的历史模式,并更好地调试和优化你的 Vue Router 应用。
彩蛋:一些小技巧
base
选项: 在配置 Vue Router 时,可以使用base
选项来指定应用的基础路径。这在部署到子目录时非常有用。scrollBehavior
选项: 可以使用scrollBehavior
选项来控制页面滚动行为。例如,可以在路由切换时滚动到页面顶部。- 路由守卫: 可以使用路由守卫(
beforeEach
、beforeResolve
、afterEach
)来控制路由的访问权限,或者在路由切换前后执行一些操作。
好了,今天的分享就到这里。希望大家有所收获!如果有什么问题,欢迎在评论区留言。下次再见!