探究对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 Router</h1>
        <p>
            <router-link to="/">主页</router-link>
            <router-link to="/apple">Go to Apple</router-link>
            <router-link to="/banana">Go to Banana</router-link>
            <router-link to="/orange">Go to Orange</router-link>
        </p>
        <P>下方为路由页面</P>
        <router-view></router-view>
    </div>
    <script>
        const Home = { template: "<div>水果</div>" };
        const Apple = { template: "<div>Apple</div>" };
        const Banana = { template: "<div>Banana</div>" };
        const Orange = { template: "<div>Orange</div>" };

        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事件监听。然而实际操作下来,没有任何作用。
请添加图片描述

不管怎么改路由,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变化的思路欢迎私信交流~

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

糠帅傅蓝烧牛肉面

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值