單頁面應用路由的兩種實現方式

什麼是 SPA?

  SPA就是單頁面應用,即single page application,經過看代碼就能夠發現,整個網站就只有一個Html文件。html

       github地址vue

 

 

WHY SPA?

  • 減少服務器壓力。 若是不用SPA,那麼咱們每次切換頁面的時候,就會向服務器發送一個請求,服務器返回一個html文件;可是若是使用了SPA,在切換時,不須要請求服務器,只要經過本地的js來切換便可。而且服務器端就不須要配置路由,徹底作到了先後端分離,這對於分工合做的意義可想而知。
  • 加強用戶體驗,增長app的使用流暢性。 作過spa的同窗都知道,使用spa以後,頁面在切換的時候很是流暢,徹底沒有那種不斷刷新的感受,而是很是快的就有了響應,由於js運行速度很快,因此js在作本地路由的時候,就會很是快。

 

SPA路由的實現方式有哪些?

  目前來講,不管是vue,仍是react,spa的路由實現方式無非就是如下二者:html5

  • hash方式。 使用location.hash和hashchange事件實現路由。 
  • history api。使用html5的history api實現,主要就是popState事件等。

  hash用的仍是比較多的,可是這種方式的url會比較醜陋,會出現 #; 而history api就能夠很好的解決這個問題,可是須要後端配置,而且因爲是html5中的api,因此兼容性還不是很好,用的時候須要肯定你的用戶,再作決定。node

  

 

SPA路由實現之hash

       本身動手,豐衣足食! 咱們本身來寫一個router,也許對齊理解就會更加明白了。通常,咱們在使用vue和react的router的時候,不難發現他們都是構造函數,而後生成一個實例,即面向對象的方式。react

  這裏固然也須要使用面向對象的方式來實現,這種方式的可擴展性、可維護性都會比較好一些。git

  github地址github

<!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 || function () {
                alert('未定義回調函數!');
            }
        }

        // 更新頁面函數
        Router.prototype.refresh = function () {
            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>

 

  上面這段代碼的思路並不難理解。express

  首先建立一個Router構造函數,初始化當前的url和一個routes對象。後端

  接着定義了三個方法:api

  • route方法 --- 該方法用於在實例化router對象以後,註冊路由,對於不一樣的路由,執行不一樣的回調函數 。
  • refresh方法 --- 在路由切換時,執行該方法刷新頁面。
  • init方法 --- 在註冊完路由以後,執行該方法,該方法主要註冊了兩個事件,尤爲是hashchange事件,很是重要。 

  效果以下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

SPA路由實現之history API

  github地址

  上面使用的hash方法實現路由當然不錯,可是也是存在必定的問題的,就是太醜~ 若是在微信中使用,看不到url倒也無所謂,可是若是在通常的瀏覽器中使用就會遇到問題了。因此這裏使用 history api來實現。 

  在html5中的history api包括兩個方法history.pushState()和history.replaceState(),包含一個事件history.onpopstate,咱們先進行簡單的介紹:

history.pushState(stateObj, title, url)

  • stateObj爲一個狀態對象,這個對象能夠被popstate事件讀取到,也能夠在history對象中獲取。
  • title爲標題,可是瀏覽器目前還沒能實現,因爲其自己是一個字符串,因此咱們使用‘’來代替便可。
  • url爲路徑。通常設定爲相對的url,絕對路徑須要保證同源。 

  pushState向瀏覽器的歷史記錄棧中壓入一個歷史記錄,因此這裏的push和pop就比較好理解了。

 

history.replaceState()

  這個就比較好理解了,接受的參數都是同樣的,做用就是替換當前歷史記錄棧中的記錄。

  

onpopstate事件  

  在瀏覽器前進、後退時觸發,通常就是歷史記錄棧中的指針改變的時候就會觸發這個事件了。

 

 

 在測試以前呢,須要知道這樣一個事情: 測試必須使用本地服務器上進行測試,若是使用file://的方式打開頁面,就會出現下面的狀況:

Uncaught SecurityError: A history state object with URL 'file:///C:/xxx/xxx/xxx/xxx.html' cannot be created in a document with origin 'null'.

 

由於pushState的url和當前的Url必須是同源的,而file://的形式是不存在同源的說法的,因此咱們必須用http://localhost的方式。

<!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;

            // 從新加載函數,這裏使用的主機是http://localhost:8088/
            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>

 

 

node部分的代碼以下:

var express = require('express');
var path = require('path')
var app = express();
 
app.get('/', function (req, res) {
   res.sendFile(path.resolve(__dirname,  './www/history.html'));
})

app.get('*', function (req, res) {
   res.sendFile(path.resolve(__dirname,  './www/history.html'));
});

const port = 8088;

var server = app.listen(port, function () {
  console.log("訪問地址爲 http://localhost:" + port)
});

 

 

最終效果以下:

 

而且能夠實現基本的前進後退功能。 

 

 

 

 

 

原創文章,未經容許,不得轉載!

相關文章
相關標籤/搜索