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


什么是 SPA?

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

       github地址

 

 

WHY SPA?

  • 減小服務器壓力。 如果不用SPA,那么我們每次切換頁面的時候,就會向服務器發送一個請求,服務器返回一個html文件;但是如果使用了SPA,在切換時,不需要請求服務器,只要通過本地的js來切換即可。並且服務器端就不需要配置路由,完全做到了前后端分離,這對於分工合作的意義可想而知。
  • 增強用戶體驗,增加app的使用流暢性。 做過spa的同學都知道,使用spa之后,頁面在切換的時候非常流暢,完全沒有那種不斷刷新的感覺,而是非常快的就有了響應,因為js運行速度很快,所以js在做本地路由的時候,就會非常快。

 

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

  目前來說,無論是vue,還是react,spa的路由實現方式無非就是以下兩者:

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

  hash用的還是比較多的,但是這種方式的url會比較丑陋,會出現 #; 而history api就可以很好的解決這個問題,但是需要后端配置,並且由於是html5中的api,所以兼容性還不是很好,用的時候需要確定你的用戶,再做決定。

  

 

SPA路由實現之hash

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

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

  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>

 

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

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

  接着定義了三個方法:

  • 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)
});

 

 

最終效果如下:

 

並且可以實現基本的前進后退功能。 

 

 

 

 

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM