单页面路由的实现方式

前端路由主要由两种方式实现:

  • hash方式 :location.hash+hashchange事件
  • history api:history.pushState()+popState事件

hash方式实现路由跳转

一个demo本地可直接使用,另外我也部署在了http://wangkehan.com/hash可以直接查看

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>router</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            margin: 0;
        }
        div.router-wrap {
            width: 100%;
            height: 100%;
            background: #fefefe;
        }
        a {
            padding: 10px;  
            color: pink;
            font-size: 25px;
            font-weight: bold;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div class="router-wrap">
        <a href="#/black">黑色</a><br>
        <a href="#/green">绿色</a><br>
        <a href="#/red">红色</a>
    </div>
    <script>
        // 创建Router构造函数
        // currentHash为当前hash值,routes为路径对象
        function Router() {
            this.currentHash = '/';
            this.routes = {};
        }

        // 注册路径,每个路径对应一个回调函数。 
        Router.prototype.route = function (path, callback) {
            this.routes[path] = callback
        }

        // 更新页面函数
        Router.prototype.refresh = function () {
            // #/green => /green
            this.currentHash = location.hash.slice(1) || '/';
            // 调用对应路径注册的更新函数
            this.routes[this.currentHash]();
        }

        // 初始化
        Router.prototype.init = function () {
            var self = this;
            window.addEventListener('load', function () {
                self.refresh();
            }, false);  

            window.addEventListener('hashchange', function () {
                self.refresh();
            });
        }
    </script>

    <script>
        var wrap = document.querySelector('.router-wrap');

        window.Router = new Router();
        Router.route('/', function () {
            wrap.style.backgroundColor = '#fefefe';
        });

        Router.route('/black', function () {
            wrap.style.backgroundColor = 'black';
        });

        Router.route('/green', function () {
            wrap.style.backgroundColor = 'green';
        });

        Router.route('/red', function () {
            wrap.style.backgroundColor = 'red';
        });

        Router.init();

    </script>
</body>
</html>

首先创建一个Router构造函数,初始化当前的url和一个routes对象。

定义了三个方法:

  • route方法 — 该方法用于在实例化router对象之后,注册路由,对于不同的路由,执行不同的回调函数 。

  • refresh方法 — 在路由切换时,执行该方法刷新页面。

  • init方法 — 在注册完路由之后,执行该方法,该方法主要注册了两个事件,主要是添加hashchange监听事件

history方式路由实现

html5中的history api包括两个方法history.pushState()history.replaceState(),包含一个事件history.onpopstate

1、history.pushState(stateObj, title, url) —向历史栈中写入数据

  1. stateObj为一个状态对象,这个对象可以被popstate事件读取到,也可以在history对象中获取。
  2. title为标题,但是浏览器目前还没能实现,由于其本身是一个字符串,所以我们使用‘’来代替即可。
  3. url为路径。一般设定为相对的url,绝对路径需要保证同源。
    pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。

2、history.replaceState(stateObj, title, url)
参数和pushState一样
区别:替换修改浏览历史中当前纪录
3、popstate事件
      定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
      只调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者调用back、forward、go方法时才会触发。
      使用:为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(stateObj)
demo:
      如果在本地测试:因为pushState的url和当前的Url必须是同源的,而file://的形式是不存在同源的说法的,所以我们必须用http://localhost的方式。
因此,为了方便查看,我将history的实现效果部署在了http://wangkehan.com/history可以直接查看

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>router</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            margin: 0;
        }
        div.router-wrap {
            width: 100%;
            height: 100%;
            background: #efefef;
        }
        a {
            display: inline-block;
            padding: 10px;  
            color: pink;
            font-size: 25px;
            font-weight: bold;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div class="router-wrap">
        <a href="/black" class="history-link">黑色</a><br>
        <a href="/green" class="history-link">绿色</a><br>
        <a href="/red" class="history-link">红色</a>
    </div>

    <script>
        // 创建Router构造函数
        function Router() {
            this.currentRoute = '';
            this.routes = {};
            this.init();
        }

        // 注册路由函数
        Router.prototype.route = function (path, callback) {

            // 根据type类型,选择相应的history api。  
            this.routes[path] = function (type) {
                if (type == 1) {
                    history.pushState({path: path}, '', path);
                } else if (type == 2) {
                    history.replaceState({path: path}, '', path);
                }
                callback();
            }
        }

        // 更新页面
        Router.prototype.refresh = function (path, type) {
            this.routes[path](type);
        }

        // 初始化
        Router.prototype.init = function () {

            var self = this;

            // 重新加载函数
            window.addEventListener('load', function () {
                self.currentRoute = location.href.slice(location.href.indexOf('/', 8));
                console.log(self.currentRoute);
                self.refresh(self.currentRoute);
            });

            // 当用户点击前进后退按钮时触发函数
            window.addEventListener('popstate', function () {
                console.log('history.state.path:', history.state.path);
                self.currentRoute = history.state.path;
                self.refresh(self.currentRoute, 2);
            }, false);

            // 对所有的link标签进行绑定事件
            var historyLinks = document.querySelectorAll('.history-link');
            for (var i = 0, len = historyLinks.length; i < len; i++) {
                historyLinks[i].onclick = function(e) {
                    // 阻止默认
                    e.preventDefault();
                    self.currentRoute = e.target.getAttribute('href');
                    self.refresh(self.currentRoute, 1);
                }
            }
        }
    </script>

    <script>
        var wrap = document.querySelector('.router-wrap');

        // 实例化Router
        window.Router = new Router();


        // 注册路由,实现相应功能
            
        Router.route('/', function () {
            wrap.style.backgroundColor = '#efefef'
        });

        Router.route('/black', function () {
            wrap.style.backgroundColor = 'black';
        });

        Router.route('/green', function () {
            wrap.style.backgroundColor = 'green';
        });

        Router.route('/red', function () {
            wrap.style.backgroundColor = 'red';
        });
    </script>
</body>
</html>

两种方式的区别

  1. pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL

  2. pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中

  3. pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串

  4. pushState可额外设置title属性供后续使用

history模式的一个问题

hash模式仅改变hash部分的内容,而hash部分是不会包含在HTTP请求中的:
http://oursite.com/#/user/id // 如重新请求只会发送http://oursite.com/
故在hash模式下遇到根据URL请求页面的情况不会有问题。
而history模式则会将URL修改得就和正常请求后端的URL一样
http://oursite.com/user/id
在此情况下重新向后端发送请求,如后端没有配置对应/user/id的路由处理,则会返回404错误。

解决方法:
      如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面

两种模式的使用场景

1、一般场景下,hash 和 history 都可以,如果更在意颜值,# 符号不在URL中展示可以选择history模式
2、history —— 利用了 HTML5 中新增的api,无法在低版本浏览器中使用

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