各位靓仔靓女们,晚上好!今天咱们不聊八卦,就来扒一扒 Vue Router 的底裤,看看它里面的 history
对象是怎么耍的。重点是 listen
和 unlisten
这俩兄弟,以及它们怎么跟浏览器的历史栈眉来眼去。
开场白:历史是个圈,还是个栈?
首先,我们要搞清楚浏览器的历史记录是个什么玩意儿。它既像个圈,可以前进后退循环;又像个栈,后进先出,一层一层叠起来。Vue Router 的 history
对象就是个老司机,负责管理这个“圈圈栈”。
history
对象:路由的掌舵人
在 Vue Router 中,history
对象负责处理路由的变化,并与浏览器的历史记录进行交互。它主要有以下几种实现方式:
HTML5 History API
(createWebHistory): 使用pushState
和replaceState
方法来修改浏览器的 URL,而不会重新加载页面。这是最常用的方式,也是我们今天的主角。Hash History
(createWebHashHistory): 使用 URL 的 hash 部分(#
)来模拟路由的变化。兼容性好,但 URL 看起来比较丑。Abstract History
(createMemoryHistory): 用于服务器端渲染 (SSR) 或测试环境中,不依赖于浏览器环境。
今天咱们主要围绕 HTML5 History API
展开,因为它最常用,也最能体现 listen
和 unlisten
的作用。
listen
:监听历史的呼吸
listen
方法的作用是注册一个监听器函数,当浏览器的历史记录发生变化时,这个函数就会被调用。 简单来说,就是给历史记录装了个窃听器,一旦有风吹草动,立刻汇报。
它的基本用法如下:
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
// ...你的路由配置
]
});
const unlisten = router.history.listen((to, from, failure) => {
// to: 即将进入的路由对象
// from: 当前导航离开的路由对象
// failure: 导航失败时的错误对象 (可选)
console.log('路由发生了变化!');
console.log('从', from.fullPath, '到', to.fullPath);
});
// 当不再需要监听时,调用 unlisten() 来取消监听
// unlisten();
上面的代码中,router.history.listen
接收一个回调函数作为参数。当用户点击浏览器的前进/后退按钮,或者通过 router.push
或 router.replace
方法进行路由跳转时,这个回调函数就会被触发。
源码剖析:listen
内部的秘密
让我们稍微深入一点,看看 listen
内部是如何实现的(简化版):
// Vue Router 源码 (简化版)
class History {
constructor() {
this.listeners = []; // 存放监听器函数的数组
}
listen(callback) {
this.listeners.push(callback);
// 返回一个取消监听的函数
return () => {
this.listeners = this.listeners.filter(listener => listener !== callback);
};
}
// 当历史记录发生变化时,调用所有监听器函数
triggerListeners(to, from, failure) {
this.listeners.forEach(listener => {
listener(to, from, failure);
});
}
}
从上面的代码可以看出,listen
方法实际上就是把回调函数存放到一个数组 listeners
中。当历史记录发生变化时,triggerListeners
方法会遍历这个数组,并依次调用其中的回调函数。
unlisten
:停止监听,耳根清净
unlisten
方法的作用是取消之前通过 listen
方法注册的监听器函数。就像拔掉了窃听器的电源,世界顿时安静了。
还记得上面的例子吗? listen
方法返回了一个函数 unlisten
。调用这个函数就可以取消监听:
const unlisten = router.history.listen((to, from) => {
console.log('路由发生了变化!');
});
// 当不再需要监听时,调用 unlisten() 来取消监听
unlisten(); // 路由变化时,不再输出任何信息
源码剖析:unlisten
的优雅转身
再来看看 unlisten
内部是如何实现的(简化版):
// Vue Router 源码 (简化版)
class History {
constructor() {
this.listeners = []; // 存放监听器函数的数组
}
listen(callback) {
this.listeners.push(callback);
// 返回一个取消监听的函数
return () => {
this.listeners = this.listeners.filter(listener => listener !== callback);
};
}
// ...
}
可以看到,unlisten
实际上是通过 filter
方法从 listeners
数组中移除对应的回调函数。这样,当历史记录发生变化时,这个回调函数就不会再被调用了。
listen
和 unlisten
如何与浏览器历史栈交互?
这才是重头戏!listen
和 unlisten
并非直接操作浏览器的历史栈,而是通过监听浏览器的 popstate
事件来实现的。
popstate
事件:历史栈变化的信号
当用户点击浏览器的前进/后退按钮时,浏览器会触发 popstate
事件。这个事件告诉我们,浏览器的历史记录发生了变化。
- Vue Router 的应对策略
Vue Router 会监听 popstate
事件,并在事件处理函数中调用 triggerListeners
方法,从而触发所有通过 listen
注册的回调函数。
让我们用代码来模拟一下这个过程:
// 模拟 Vue Router 的 History 对象
class MockHistory {
constructor() {
this.listeners = [];
this.currentPath = '/';
}
listen(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(listener => listener !== callback);
};
}
push(path) {
this.currentPath = path;
window.history.pushState({ path }, '', path); // 修改浏览器历史记录
this.triggerListeners(path, this.currentPath);
}
replace(path) {
this.currentPath = path;
window.history.replaceState({path}, '', path); // 替换当前历史记录
this.triggerListeners(path, this.currentPath);
}
triggerListeners(to, from) {
this.listeners.forEach(listener => {
listener({ fullPath: to }, {fullPath: from}); // 简化版的 to 和 from 对象
});
}
}
// 创建 History 对象
const history = new MockHistory();
// 注册监听器
const unlisten = history.listen((to, from) => {
console.log('路由发生了变化!');
console.log('从', from.fullPath, '到', to.fullPath);
});
// 监听 popstate 事件
window.addEventListener('popstate', (event) => {
const path = window.location.pathname;
history.triggerListeners(path, history.currentPath);
history.currentPath = path;
});
// 模拟路由跳转
history.push('/about'); // 输出:路由发生了变化! 从 / 到 /about
history.push('/contact'); // 输出:路由发生了变化! 从 /about 到 /contact
// 点击浏览器的后退按钮
// 输出:路由发生了变化! 从 /contact 到 /about
上面的代码中,我们模拟了一个简化的 History
对象,并监听了 popstate
事件。当用户点击浏览器的后退按钮时,popstate
事件会被触发,triggerListeners
方法会被调用,从而触发所有通过 listen
注册的回调函数。
pushState
和 replaceState
:操控历史的魔法棒
Vue Router 使用 pushState
和 replaceState
方法来修改浏览器的历史记录。
pushState
:新增一条历史记录
pushState
方法会在浏览器的历史栈中新增一条记录。当用户点击浏览器的后退按钮时,会回到上一条记录。
replaceState
:替换当前历史记录
replaceState
方法会替换当前的历史记录。当用户点击浏览器的后退按钮时,不会回到被替换的记录。
表格总结:listen
、unlisten
和历史栈的三角恋
对象/事件 | 作用 | 与历史栈的关系 |
---|---|---|
listen |
注册一个监听器函数,当路由发生变化时被调用 | 监听 popstate 事件,间接响应历史栈的变化 |
unlisten |
取消之前注册的监听器函数 | 停止监听 popstate 事件 |
popstate |
浏览器事件,当用户点击前进/后退按钮时触发 | 触发 listen 注册的回调函数 |
pushState |
修改浏览器历史记录,新增一条记录 | 直接修改历史栈 |
replaceState |
修改浏览器历史记录,替换当前记录 | 直接修改历史栈 |
实际应用场景:listen
和 unlisten
的用武之地
- 页面统计: 监听路由变化,统计用户访问的页面。
- 滚动条位置保存: 在路由离开时保存滚动条位置,在路由进入时恢复滚动条位置。
- 权限控制: 监听路由变化,检查用户是否有权限访问当前页面。
- 数据预取: 在路由进入之前,预取页面所需的数据。
注意事项:unlisten
的重要性
如果不及时调用 unlisten
方法取消监听,可能会导致内存泄漏。因为监听器函数会一直存在于内存中,即使组件已经被销毁。
总结:历史的掌控者
listen
和 unlisten
是 Vue Router 中非常重要的两个方法,它们负责监听路由的变化,并与浏览器的历史栈进行交互。通过理解它们的原理,我们可以更好地掌握 Vue Router 的路由机制,并编写出更加健壮和高效的 Vue 应用。
好了,今天的讲座就到这里。希望大家对 Vue Router 的 history
对象有了更深入的了解。下次有机会,我们再来聊聊 Vue Router 的其他有趣特性。 大家晚安!