探究对VueRouter的Hash模式进行外部监听

探究对VueRouter的Hash模式进行外部监听(未实现)

VueRouter的hash模式的普遍印象是监听hashchange事件从而改变页面显示的组件,然而在真实场景下会出现hashchange事件无效的情况,本文据此展来了一系列实践操作。

搭建Demo

通过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>

测试hashchange

window.addEventListener("hashchange", () => {
    console.log("页面Hash值发生变化!");
});

很简单嘛,给window添加hashchange事件监听。然而实际操作下来,没有任何作用。
探究对VueRouter的Hash模式进行外部监听_第1张图片

不管怎么改路由,hashchange事件都没有被触发,打开控制台输入location.hash却能看见hash产生了变化。

探究对VueRouter的Hash模式进行外部监听_第2张图片

探究原因

这篇文章有所描述: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变化的思路欢迎私信交流~

你可能感兴趣的:(前端)