各位观众老爷,大家好!今天咱们来聊聊 Vue Router 里一个非常重要的角色—— history 对象,特别是它的 listen 和 unlisten 方法。这俩兄弟,看似简单,实则肩负重任,直接关系到你的 Vue 应用如何与浏览器的历史记录愉快地玩耍。
开场白:历史的轮子与 Vue Router 的故事
话说,咱们在浏览器里冲浪的时候,前进后退按钮那可是神器。每次点击,浏览器都会在它的历史记录里翻来覆去,带你回到过去,或者走向未来。而 Vue Router,作为前端路由的扛把子,自然也要跟这历史记录打交道。history 对象,就是 Vue Router 操纵历史记录的利器。
history 对象,不是你随便捏泥巴捏出来的,它其实是浏览器提供的 History API 的封装。这 API 允许我们以编程的方式访问和操作浏览器的历史栈,而无需真的重新加载页面。
第一幕:history 对象的身世之谜
在 Vue Router 中,history 对象主要有三种类型:
HTML5History(也叫createWebHistory): 这是最推荐的模式,利用了pushState和replaceState这两个猛将,可以在不刷新页面的情况下改变 URL,并且可以记录到浏览器的历史栈中。HashHistory(也叫createWebHashHistory): 这个老古董,利用 URL 中的 hash 部分(#符号后面的内容)来模拟路由。好处是兼容性好,坏处是 URL 看起来不太优雅,搜索引擎也不太喜欢。MemoryHistory(也叫createMemoryHistory): 这个家伙不会真的跟浏览器历史记录打交道,而是把路由状态保存在内存里。适合 SSR (服务端渲染) 和测试环境。
今天咱们主要聚焦 HTML5History,毕竟它是主流,也是 Vue Router 官方推荐的。
第二幕:listen 大法的修炼
listen 方法,顾名思义,就是“监听”。它允许你注册一个回调函数,当路由发生变化时,这个回调函数就会被触发。这个“路由变化”,指的不仅仅是 router.push 或 router.replace 引起的,也包括用户点击浏览器的前进后退按钮。
咱们先来看看 HTML5History 里的 listen 方法是怎么实现的 (简化版):
class HTML5History {
constructor(router, base) {
this.router = router;
this.base = base;
this.listeners = []; // 用于存放监听函数
this.current = { path: '/', fullPath: '/', query: {}, params: {} }; // 当前路由状态
}
listen(callback) {
this.listeners.push(callback);
return () => { // 返回一个 unlisten 函数
this.listeners = this.listeners.filter(listener => listener !== callback);
};
}
// 内部方法,用于通知所有监听者
_onTransition(to, from, trigger) {
this.listeners.forEach(listener => {
listener(to, from, trigger);
});
}
push(to) {
// ... 省略复杂的路由跳转逻辑
window.history.pushState({ key: Date.now() }, '', to); // 使用 pushState 修改 URL
const next = this.router.resolve(to);
const from = this.current;
this.current = next;
this._onTransition(next, from, NavigationType.push); // 通知监听者
}
replace(to) {
// ... 省略复杂的路由跳转逻辑
window.history.replaceState({ key: Date.now() }, '', to); // 使用 replaceState 修改 URL
const next = this.router.resolve(to);
const from = this.current;
this.current = next;
this._onTransition(next, from, NavigationType.replace); // 通知监听者
}
}
const NavigationType = {
push: 'push',
replace: 'replace',
pop: 'pop',
};
这段代码的关键点:
this.listeners: 这是一个数组,用于存储所有注册的监听函数。listen(callback): 这个方法把传入的回调函数callback添加到this.listeners数组中。return () => { ... }:listen方法返回一个函数,这个函数的作用是“取消监听”,也就是从this.listeners数组中移除对应的回调函数。_onTransition(to, from, trigger): 这是一个内部方法,当路由发生变化时,它会遍历this.listeners数组,依次调用所有的监听函数。push(to)和replace(to): 这两个方法负责修改 URL,并调用_onTransition通知监听者。
举个栗子:
const router = new VueRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
{ path: '/about', component: { template: '<div>About</div>' } },
],
});
const unlisten = router.history.listen((to, from) => {
console.log(`路由从 ${from.path} 变成了 ${to.path}`);
});
router.push('/about'); // 控制台输出:路由从 / 变成了 /about
unlisten(); // 取消监听
router.push('/'); // 控制台不再输出任何内容
这个例子演示了如何使用 listen 方法注册一个监听函数,并在路由变化时得到通知。同时,也演示了如何使用 unlisten 函数取消监听。
第三幕:unlisten 剑法的精髓
unlisten 方法,是 listen 方法返回的那个函数。它的作用是“取消监听”,也就是从 this.listeners 数组中移除对应的回调函数。
为什么要取消监听呢?
- 避免内存泄漏: 如果你注册的监听函数引用了组件实例或其他较大的对象,而你又没有及时取消监听,那么这些对象可能无法被垃圾回收,导致内存泄漏。
- 避免重复执行: 在某些情况下,你可能只需要监听一段时间,或者只需要在特定的组件中监听。如果忘记取消监听,可能会导致回调函数被多次执行,产生意想不到的 bug。
unlisten 的实现非常简单,就是从 this.listeners 数组中移除对应的回调函数:
// 在上面的 HTML5History 类中
listen(callback) {
this.listeners.push(callback);
return () => { // 返回一个 unlisten 函数
this.listeners = this.listeners.filter(listener => listener !== callback);
};
}
注意事项:
unlisten函数只能取消同一个回调函数的监听。如果你注册了多个相同的回调函数,unlisten只会取消其中一个。- 一定要在组件销毁之前取消监听,避免内存泄漏。通常在
beforeUnmount或unmounted钩子函数中调用unlisten。
第四幕:与浏览器历史栈的爱恨情仇
HTML5History 能够与浏览器历史栈进行交互,主要依靠的是 pushState 和 replaceState 这两个 API。
pushState(state, title, url): 这个方法会在浏览器的历史栈中添加一个新的条目。state是一个与新条目关联的 JavaScript 对象,title是页面的标题 (通常可以为空字符串),url是新的 URL。replaceState(state, title, url): 这个方法会替换浏览器历史栈中的当前条目。参数和pushState相同。
Vue Router 在调用 push 和 replace 方法时,会分别调用 pushState 和 replaceState 来修改 URL,并更新浏览器的历史栈。
但是,问题来了:用户点击浏览器的前进后退按钮时,浏览器并不会主动通知 Vue Router。Vue Router 需要监听 popstate 事件,才能知道用户点击了前进后退按钮。
class HTML5History {
constructor(router, base) {
// ... 省略之前的代码
// 监听 popstate 事件
this.cleanup = () => {
window.removeEventListener('popstate', this.popStateHandler);
};
this.popStateHandler = ({ state }) => {
if (state) {
// ... 省略复杂的路由跳转逻辑,包括解析 URL,匹配路由等
const next = this.router.resolve(window.location.pathname);
const from = this.current;
this.current = next;
this._onTransition(next, from, NavigationType.pop); // 通知监听者
} else {
// state 为 null,说明是初始化时触发的 popstate 事件,忽略
}
};
window.addEventListener('popstate', this.popStateHandler);
}
destroy() {
this.cleanup();
}
}
这段代码的关键点:
popstate事件: 当用户点击浏览器的前进后退按钮时,会触发popstate事件。popStateHandler: 这个函数是popstate事件的处理函数。它会解析 URL,匹配路由,并调用_onTransition通知监听者。state:popstate事件对象有一个state属性,它包含了调用pushState或replaceState时传入的state对象。Vue Router 利用这个state对象来判断是否需要处理popstate事件。
表格总结:
| 方法/事件 | 作用 | 触发时机 |
|---|---|---|
pushState |
在浏览器历史栈中添加新的条目 | 调用 router.push 时 |
replaceState |
替换浏览器历史栈中的当前条目 | 调用 router.replace 时 |
popstate |
当用户点击浏览器的前进后退按钮时触发 | 用户点击浏览器的前进后退按钮 |
listen |
注册一个回调函数,当路由发生变化时被调用 | 调用 router.push、router.replace 或用户点击浏览器的前进后退按钮导致路由变化时 |
unlisten |
取消监听,移除之前注册的回调函数 | 组件销毁之前,避免内存泄漏 |
第五幕:实战演练
咱们来做一个简单的 demo,演示如何使用 listen 和 unlisten 来实现一个“路由守卫”的功能。
<template>
<div>
<router-link to="/">Home</router-link> |
<router-link to="/admin">Admin</router-link>
<router-view />
</div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { useRouter, useRoute } from 'vue-router';
export default {
setup() {
const router = useRouter();
const route = useRoute();
const isLoggedIn = ref(false); // 模拟用户登录状态
let unlisten = null;
onMounted(() => {
unlisten = router.history.listen((to, from) => {
if (to.path === '/admin' && !isLoggedIn.value) {
console.log('没有权限访问 Admin 页面,跳转到首页');
router.push('/');
}
});
});
onBeforeUnmount(() => {
if (unlisten) {
unlisten(); // 组件销毁之前取消监听
}
});
return { isLoggedIn };
},
};
</script>
这个例子中,我们在 onMounted 钩子函数中注册了一个监听函数。当路由切换到 /admin 页面时,如果用户没有登录,就会被重定向到首页。在 onBeforeUnmount 钩子函数中,我们取消了监听,避免内存泄漏。
剧终:总结与展望
今天咱们深入剖析了 Vue Router 中 history 对象的 listen 和 unlisten 机制,以及它们如何与浏览器历史栈进行交互。希望通过今天的讲解,大家能够对 Vue Router 的内部原理有更深入的了解,从而更好地使用 Vue Router 构建你的 Vue 应用。
记住,listen 和 unlisten 是两把利剑,用好了可以让你在路由的世界里游刃有余,用不好可能会让你陷入内存泄漏的泥潭。
好了,今天的讲座就到这里。感谢大家的观看,我们下期再见!