spa 路由简易原理

Posted by franki on October 31, 2019

spa 路由简易原理及其实现

浏览器 history 和 hash

hash

具体指的是浏览器url # 后面的内容,例如 https://www.google.com/#abc 的hash值为abc 浏览器会记录hash的变化,以至于前进后退可以使用

具体表现:

1 hash 变化可以随便改变更新url,不会引起服务器的更新,因为hash的变化并不会引起http之类的请求,所以hash可以频繁刷新

2 浏览器的onhashchange可以监听到hash的变化,从而去触发一些动作

html5 新的的两个hash方法

pushState()

replaceState

以上两者都可以改变url的地址 区别是前者可以增加history.length 后者则不会

虽然以上两者都可以改变url和页面标题,但是却不会让页面刷新和更改页面的展示,因为并没有触发onpopstate事件

popstate事件 只有触发了浏览器行为的时候(例如点击浏览器的前景和后退),就会触发popstate事件,其他的则不会

reload() replace() location 三者区别

location.reload() 取的是客户端缓存的页面

location.replace() 重新请求加载url指向的页面

location.href = url 等于pushstate修改url,会创建一条新的记录

简单 hash router 实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class Router {
            constructor() {
                this.routes = {};
                this.currentUrl = '';
            }

            // routes来存放不同路由的回调函数
            route (path, callback) {
                this.routes[path] = callback || function() {};
            }

            updateView() {
               this.currentUrl = location.hash.slice(1);
               this.routes[this.currentUrl] && this.routes[this.currentUrl]();
            }

            // 初始化route,当load加载后更新页面
            // 监听hashchange当hash改变时更新页面
            init() {
                window.addEventListener('load', this.updateView.bind(this), false);
                window.addEventListener('hashchange', this.updateView.bind(this), false);
            }
        }
    </script>
</head>
<body>
    <div id='app'>
        <ul>
            <li>
                <a href="#/">home</a>
            </li>
            <li>
                <a href="#/about">about</a>
            </li>
            <li>
                <a href="#/topics">topics</a>
            </li>
        </ul>
        <div id='content'></div>
    </div>
    <script>
        const router = new Router();
        router.init();
        router.route('/', function() {
            document.getElementById('content').innerHTML = 'home';
        });
        router.route('/about', function () {
            document.getElementById('content').innerHTML = 'about';
        });
        router.route('/topics', function () {
            document.getElementById('content').innerHTML = 'topics';
        });
    </script>
</body>
</html>

简易 history 路由实现

hash 的改变可以出发hashchange事件,而history的改变并不触发事件,使得不能使用去监听history的改变而做出任何的改变

换个想法:引起url变化的原因无非有三个

1 浏览器的前进后退(可以通过popstate来监听)

2 a标签的点击

3 js代码history.pushState() 或者 history.replaceState()函数

简易实现如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class Router {
            constructor() {
                this.routes = {};
                this.currentUrl = '';
            }

            // routes来存放不同路由的回调函数
            route(path, callback) {
                this.routes[path] = callback || function () { };
            }

            updateView() {
                this.currentUrl = location.hash.slice(1);
                this.routes[this.currentUrl] && this.routes[this.currentUrl]();
            }

            bindLind() {
                const alllink = document.querySelectorAll('a[data-href]');
                for (let i = 0; i < alllink.length; i++) {
                    const current = alllink[i];
                    current.addEventListener(
                        'click',
                        e => {
                            e.preventDefault();
                            const url = current.getAttribute('data-href');
                            history.pushState({}, null, url);
                            this.updateView(url);
                        },
                        false
                    );
                }
            }

            // 初始化route,当load加载后更新页面
            // 监听popstate浏览器前进后退
            init() {
                // 该函数绑定a标签,并阻止默认行为
                this.bindLind();
                window.addEventListener('load', this.updateView.bind(this), false);
                window.addEventListener('popstate', this.updateView.bind(this), false);
            }
        }
    </script>
</head>

<body>
    <div id='app'>
        <ul>
            <li>
                <a data-href="#/">home</a>
            </li>
            <li>
                <a data-href="#/about">about</a>
            </li>
            <li>
                <a data-href="#/topics">topics</a>
            </li>
        </ul>
        <div id='content'></div>
    </div>
    <script>
        const router = new Router();
        router.init();
        router.route('/', function () {
            document.getElementById('content').innerHTML = 'home';
        });
        router.route('/about', function () {
            document.getElementById('content').innerHTML = 'about';
        });
        router.route('/topics', function () {
            document.getElementById('content').innerHTML = 'topics';
        });
    </script>
</body>

</html>

小结: 有了对hash 和 history的基础了解后,再去研究当前流行的spa就非常容易了,简单来说原理就是通过hash的变化及其history的变化,来触发一系列的事件来更新页面。