各位观众老爷,大家好!今天咱们来聊聊 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
是两把利剑,用好了可以让你在路由的世界里游刃有余,用不好可能会让你陷入内存泄漏的泥潭。
好了,今天的讲座就到这里。感谢大家的观看,我们下期再见!