什么是 SPA?
SPA就是單頁面應用,即single page application,通過看代碼就可以發現,整個網站就只有一個Html文件。
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的時候,不難發現他們都是構造函數,然后生成一個實例,即面向對象的方式。
這里當然也需要使用面向對象的方式來實現,這種方式的可擴展性、可維護性都會比較好一些。
<!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
上面使用的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) });
最終效果如下:
並且可以實現基本的前進后退功能。
原創文章,未經允許,不得轉載!