VueRouter的hash模式的普遍印象是监听hashchange事件从而改变页面显示的组件,然而在真实场景下会出现hashchange事件无效的情况,本文据此展来了一系列实践操作。
通过serve开启一个本地服务器对外暴露一个html文件可以快速搭建Demo
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hash路由问题title>
<script src="http://localhost:3000/libs/vue.global.js">script>
<script src="http://localhost:3000/libs/vue-router.global.js">script>
head>
<body>
<div id="app">
<h1>Vue Routerh1>
<p>
<router-link to="/">主页router-link>
<router-link to="/apple">Go to Applerouter-link>
<router-link to="/banana">Go to Bananarouter-link>
<router-link to="/orange">Go to Orangerouter-link>
p>
<P>下方为路由页面P>
<router-view>router-view>
div>
<script>
const Home = { template: "水果" };
const Apple = { template: "Apple" };
const Banana = { template: "Banana" };
const Orange = { template: "Orange" };
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes: [
{ path: "/", component: Home },
{ path: "/apple", component: Apple },
{ path: "/banana", component: Banana },
{ path: "/orange", component: Orange },
],
});
const app = Vue.createApp({});
app.use(router);
app.mount("#app");
script>
body>
html>
window.addEventListener("hashchange", () => {
console.log("页面Hash值发生变化!");
});
很简单嘛,给window添加hashchange事件监听。然而实际操作下来,没有任何作用。
不管怎么改路由,hashchange事件都没有被触发,打开控制台输入location.hash
却能看见hash产生了变化。
这篇文章有所描述:https://segmentfault.com/q/1010000040105060
vue-router的hash表面上是改变hash值,实际上调用的却是pushState和replaceState的API,能够在不触发hashchange事件的情况下替换hash值。
既然他使用的是pushState和replaceState,那么监听这两个不就行了。很遗憾,原生并不支持这两个事件的监听。原生支持的是popState,此事件会在back()的情况下触发,pushState和replaceState并不会触发此事件。
在Vue-Router源码中可以看到,他手动重写了pushState和replaceState,为其添加了事件监听器,从而实现路由跳转。
function useHistoryListeners(base, historyState, currentLocation, replace) {
let listeners = [];
let teardowns = [];
// TODO: should it be a stack? a Dict. Check if the popstate listener
// can trigger twice
let pauseState = null;
const popStateHandler = ({ state, }) => {
const to = createCurrentLocation(base, location);
const from = currentLocation.value;
const fromState = historyState.value;
let delta = 0;
if (state) {
currentLocation.value = to;
historyState.value = state;
// ignore the popstate and reset the pauseState
if (pauseState && pauseState === from) {
pauseState = null;
return;
}
delta = fromState ? state.position - fromState.position : 0;
}
else {
replace(to);
}
// Here we could also revert the navigation by calling history.go(-delta)
// this listener will have to be adapted to not trigger again and to wait for the url
// to be updated before triggering the listeners. Some kind of validation function would also
// need to be passed to the listeners so the navigation can be accepted
// call all listeners
listeners.forEach(listener => {
listener(currentLocation.value, from, {
delta,
type: NavigationType.pop,
direction: delta
? delta > 0
? NavigationDirection.forward
: NavigationDirection.back
: NavigationDirection.unknown,
});
});
};
function pauseListeners() {
pauseState = currentLocation.value;
}
function listen(callback) {
// set up the listener and prepare teardown callbacks
listeners.push(callback);
const teardown = () => {
const index = listeners.indexOf(callback);
if (index > -1)
listeners.splice(index, 1);
};
teardowns.push(teardown);
return teardown;
}
function beforeUnloadListener() {
const { history } = window;
if (!history.state)
return;
history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '');
}
function destroy() {
for (const teardown of teardowns)
teardown();
teardowns = [];
window.removeEventListener('popstate', popStateHandler);
window.removeEventListener('beforeunload', beforeUnloadListener);
}
// set up the listeners and prepare teardown callbacks
window.addEventListener('popstate', popStateHandler);
// TODO: could we use 'pagehide' or 'visibilitychange' instead?
// https://developer.chrome.com/blog/page-lifecycle-api/
window.addEventListener('beforeunload', beforeUnloadListener, {
passive: true,
});
return {
pauseListeners,
listen,
destroy,
};
}
本次操作对vue-router有了更深的理解,如果有朋友有监听vue-router内hash变化的思路欢迎私信交流~