监听window.onpopstate事件实现简单history router

发布时间 2023-06-02 12:04:59作者: pangqianjin

一、存在的问题

  1. 刷新网页后,可能会报404(尤其是VSCode使用live server插件预览,因为该html文件没有与端口绑定)
  2. 开始时,需要重定向到一个路由

二、原理

  1. 通过监听
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>History Router Demo</title>
    <style>
        #root {
            text-align: center;
        }

        .comment {
            color: red;
        }
    </style>
</head>

<body>
    <div id="root">
        <div class="navbar">
            <span class="link" data-href="/home">首页</span>
            <span class="link" data-href="/article">文章</span>
            <span class="link" data-href="/image">图片</span>
            <span class="link" data-href="/comment">评论</span>
        </div>
        <div class="routerView">
            我是首页
        </div>
    </div>
    <script type="module">
        const router = {
            '/notFound': "<div>404 Not Found</div>",
            '/home': `我是首页`,
            '/article': `<p>这是一个段落。</p>
                        <p>这是一个段落。</p>
                        <p>这是一个段落。</p>`,
            '/image': `<img src="https://www.runoob.com/images/logo.png" width="258" height="39" />`,
            '/comment': `<div class="comment">这是一条评论</div>`,
        };

        class HistoryRouter {
            constructor(router, viewContainer) {
                this.router = router;
                this.viewContainer = viewContainer;
                this.historyStack = ['/home']; // 历史记录
                this.replace({pathname: '/home', search: '', state: null}); // 重定向一下
                this.setTemplate();
            }

            get stack() {
                return this.historyStack;
            }

            set stack(newValue) {
                this.historyStack = newValue;
                this.setTemplate();
            }

            setTemplate() {
                const length = this.historyStack.length;
                const pathname = this.historyStack[length - 1] || '/notFound';
                this.viewContainer.innerHTML = this.router[pathname];
            }

            push({ pathname, search, state }) {
                window.history.pushState(state, "", pathname + search);
                this.stack = [...this.stack, pathname];
            }

            back() {
                window.history.back();
                const length = this.stack.length;
                if (length > 0) {
                    this.stack = this.stack.slice(0, length - 1);
                }
            }

            replace({ pathname, search, state }) {
                window.history.replaceState(state, "", pathname + search);
                const length = this.stack.length;
                if (length > 0) {
                    const stackCopy = this.stack.slice(0, length - 1);
                    stackCopy.push(pathname)
                    this.stack = stackCopy;
                }
            }
        }

        (function () {
            const routerView = document.querySelector('.routerView');
            const historyRouter = new HistoryRouter(router, routerView);

            const links = document.getElementsByClassName('link');
            for (const link of links) {
                link.addEventListener('click', (e) => {
                    const href = e.target.getAttribute('data-href');
                    historyRouter.replace({ pathname: href, search: window.location.search, state: null });
                })
            }
        })();
    </script>
</body>

</html>