各位靓仔靓女,晚上好!我是今晚的讲师,咱们今天的主题是:Vue Router 源码里的 history
对象,重点聊聊它的 listen
和 unlisten
这哥俩,以及它们怎么跟浏览器的历史栈眉来眼去的。
准备好了吗?咱们要开车了!
开场白:history
是个啥?
在 Vue Router 的世界里,history
对象可不是指你过去的光辉岁月,而是指浏览器提供的历史记录接口。它允许我们访问浏览器的会话历史,并且可以操作它,比如前进、后退、添加新的历史记录等等。
Vue Router 需要利用 history
对象来实现单页应用 (SPA) 的路由功能,这样才能在不刷新页面的情况下,切换不同的“页面”。
history
的几种姿势
在 Vue Router 中,我们常见的 history
实现方式有三种:
HTML5 History Mode
(createWebHistory): 使用history.pushState
和history.replaceState
来操作 URL,URL 看起来跟正常的网站一样,没有#
号。 这是推荐的姿势。Hash Mode
(createWebHashHistory): 使用 URL 中的#
号来模拟路由。兼容性好,但 URL 比较丑。Memory History Mode
(createMemoryHistory): 不与浏览器 URL 交互,路由信息存储在内存中。主要用于服务端渲染 (SSR) 和测试环境。
咱们今天主要讲 HTML5 History Mode
,因为它最常用,也最能体现 listen
和 unlisten
的作用。
listen
: 监听历史记录变化的小雷达
history
对象本身提供了一些事件,比如 popstate
事件,当用户点击浏览器的前进/后退按钮时,这个事件会被触发。但是,Vue Router 需要更精细的控制,它需要知道每一次路由变化,并执行相应的操作 (比如更新组件、滚动到页面顶部等等)。
所以,Vue Router 自己封装了一个 listen
函数,用于监听历史记录的变化。这个 listen
函数本质上是一个观察者模式的实现。
// 简化版的 listen 函数 (来自 Vue Router 源码的灵感)
function createHistoryListener() {
let listeners = [];
function listen(callback) {
listeners.push(callback);
return () => { // 返回 unlisten 函数
listeners = listeners.filter(cb => cb !== callback);
};
}
function trigger(location, state) {
for (const listener of listeners) {
listener(location, state);
}
}
return {
listen,
trigger,
};
}
const historyListener = createHistoryListener();
// 注册监听器
const unlisten = historyListener.listen((location, state) => {
console.log('路由变化了!', location, state);
// 在这里执行路由更新的操作
});
// 模拟路由变化
historyListener.trigger({ path: '/about' }, { data: 'some data' });
// 取消监听
unlisten();
// 再次模拟路由变化 (不会触发监听器)
historyListener.trigger({ path: '/contact' }, { data: 'other data' });
上面的代码模拟了一个简单的 listen
函数。它内部维护了一个 listeners
数组,用于存储所有的回调函数。
listen(callback)
: 接收一个回调函数callback
,并将它添加到listeners
数组中。 它返回一个unlisten
函数,用于取消监听。trigger(location, state)
: 当路由发生变化时,调用trigger
函数,它会遍历listeners
数组,并依次执行每个回调函数,并将新的location
和state
作为参数传递给回调函数。
Vue Router 源码里的 listen
Vue Router 的 history
对象里也有类似的 listen
函数。 它的作用是:
- 监听浏览器的
popstate
事件: 当用户点击浏览器的前进/后退按钮时,触发popstate
事件。 - 调用
trigger
函数: 在popstate
事件的回调函数中,调用trigger
函数,通知所有注册的监听器。
// Vue Router 源码片段 (简化)
// 创建 HTML5 History
function createWebHistory(base) {
//...
const history = {
//...
listen: (callback) => {
listeners.push(callback);
return () => {
remove(listeners, callback);
};
},
//...
}
// 监听 popstate 事件
window.addEventListener('popstate', (event) => {
//...
for (const listener of listeners) {
listener(location, state);
}
//...
});
return history;
}
unlisten
:取消监听的橡皮擦
unlisten
函数是 listen
函数的返回值。 它的作用是:
- 从
listeners
数组中移除对应的回调函数。 - 防止内存泄漏: 如果不再需要监听路由变化,就应该及时调用
unlisten
函数,否则回调函数会一直保存在内存中,导致内存泄漏。
// 再次回顾简化版的 listen 函数
function createHistoryListener() {
let listeners = [];
function listen(callback) {
listeners.push(callback);
return () => { // 返回 unlisten 函数
listeners = listeners.filter(cb => cb !== callback);
};
}
function trigger(location, state) {
for (const listener of listeners) {
listener(location, state);
}
}
return {
listen,
trigger,
};
}
在 Vue Router 源码中,unlisten
函数的实现也类似,就是从 listeners
数组中移除对应的回调函数。
listen
和 unlisten
如何与浏览器历史栈交互?
listen
和 unlisten
并不是直接与浏览器历史栈交互,而是与浏览器的 popstate
事件交互。
当用户点击浏览器的前进/后退按钮时,浏览器会触发 popstate
事件。 Vue Router 的 history
对象会监听 popstate
事件,并在事件回调函数中,调用 trigger
函数,通知所有注册的监听器。
这些监听器 (比如 Vue Router 的内部路由逻辑) 会根据新的 URL,更新组件、滚动到页面顶部等等,从而实现路由切换的效果。
表格总结 listen
和 unlisten
特性 | listen |
unlisten |
---|---|---|
作用 | 注册一个回调函数,监听历史记录的变化。 | 取消监听,从监听列表中移除回调函数。 |
参数 | 回调函数 (接收 location 和 state 作为参数) | 无参数 |
返回值 | unlisten 函数 |
无返回值 |
触发时机 | 每次历史记录发生变化时 (通过 trigger 函数) |
当你不再需要监听路由变化时,手动调用。 |
与浏览器交互 | 间接通过 popstate 事件。 |
无直接交互。 |
重要性 | 实现路由监听的核心函数。 | 防止内存泄漏,保持应用性能的关键函数。 |
代码示例:Vue 组件中使用 listen
和 unlisten
<template>
<div>
当前路由: {{ currentRoute }}
</div>
</template>
<script>
import { useRoute, useRouter } from 'vue-router';
import { ref, onMounted, onUnmounted } from 'vue';
export default {
setup() {
const route = useRoute();
const router = useRouter();
const currentRoute = ref(route.path);
let unlisten = null; // 保存 unlisten 函数
onMounted(() => {
// 注册监听器
unlisten = router.history.listen((to, from) => {
console.log('路由从', from.path, '变化到', to.path);
currentRoute.value = to.path;
});
});
onUnmounted(() => {
// 组件卸载时,取消监听
if (unlisten) {
unlisten();
unlisten = null; // 避免重复调用
}
});
return {
currentRoute,
};
},
};
</script>
在这个例子中,我们在组件的 onMounted
钩子函数中注册了 listen
监听器,并在 onUnmounted
钩子函数中调用 unlisten
函数来取消监听。 这样可以确保组件在卸载时,不会再收到路由变化的通知,从而避免潜在的错误和内存泄漏。
进阶:state
参数
listen
函数的回调函数会接收两个参数:location
和 state
。 location
包含了新的 URL 信息,state
包含了与该 URL 相关联的状态数据。
state
数据可以通过 history.pushState
和 history.replaceState
方法来设置。
// 设置 state 数据
history.pushState({ data: 'some data' }, '', '/about');
// 在 listen 回调函数中,可以访问 state 数据
router.history.listen((to, from, state) => {
console.log('state 数据:', state); // { data: 'some data' }
});
state
数据可以用于存储一些与路由相关的临时数据,比如滚动位置、表单数据等等。 当用户点击浏览器的前进/后退按钮时,这些数据也会被恢复,从而提供更好的用户体验。
注意事项
- 及时取消监听: 在组件卸载或者不再需要监听路由变化时,一定要及时调用
unlisten
函数,防止内存泄漏。 - 避免重复注册监听器: 确保只注册一次监听器,避免多次执行相同的回调函数。
Hash Mode
的listen
: 在Hash Mode
下,Vue Router 监听的是hashchange
事件,而不是popstate
事件。listen
和unlisten
的机制是类似的,只是事件类型不同。
总结
listen
和 unlisten
是 Vue Router 源码中非常重要的两个函数。 它们实现了路由监听的功能,使得 Vue Router 能够感知到浏览器的历史记录变化,并执行相应的操作。
通过理解 listen
和 unlisten
的机制,我们可以更好地理解 Vue Router 的内部工作原理,并能够更灵活地使用 Vue Router 来构建复杂的单页应用。
好了,今天的讲座就到这里。 希望大家有所收获! 记住,写代码要细心,要记得及时清理垃圾 (取消监听),这样才能写出高性能、高质量的代码!
有问题欢迎提问! 如果没有问题,那就…下课!