上篇文章簡單介紹了html5的history api,這篇將就進入實戰環節。看看在history api的輔助下,目前流行的前端SPA框架路由是怎麼實現的。
1.什麼是SPA
首先,什麼事spa呢?一句話概括:spa就是單頁面應用,即:single page application。通過看源碼就可以發現,整個網站就只有一個html文件。
那麼,spa有什麼優勢呢?爲什麼前端要採用這種技術選型呢?小編總結了下,覺得有點有一下幾點:
a.減小服務器壓力。 如果不用SPA,那麼我們每次切換頁面的時候,就會向服務器發送一個請求,服務器返回一個html文件;但是如果使用了SPA,在切換時,不需要請求服務器,只要通過本地的js來切換即可。並且服務器端就不需要配置路由,完全做到了前後端分離,這對於分工合作的意義可想而知。
b.增強用戶體驗,增加app的使用流暢性。 做過spa的同學都知道,使用spa之後,頁面在切換的時候非常流暢,完全沒有那種不斷刷新的感覺,而是非常快的就有了響應,因爲js運行速度很快,所以js在做本地路由的時候,就會非常快。
然則,牛逼的spa就沒有缺點了嗎?答案是有的。
a.SEO問題沒有html抓不到什麼。。。
b.剛開始的時候加載可能慢很多
c.用戶操作需要寫邏輯,前進、後退等;
d.頁面複雜度提高很多,複雜邏輯難度成倍
a,b兩點可以通過服務端渲染克服。目前流行的前端框架也已經能很好的支持服務端渲染。d卻要根據具體業務需求合理的進行頁面分割、組件劃分。至於c,咱們就結合昨天所講的history api看看具體如何解決。
2.SPA路由實現有哪些
目前來說,無論是vue,還是react,spa的路由實現方式無非就是以下兩者:
1.hash方式。 使用location.hash和hashchange事件實現路由。
2.history api。使用html5的history api實現,主要就是popState事件等。
hash用的還是比較多的,但是這種方式的url會比較醜陋,會出現 #; 而history api就可以很好的解決這個問題,但是需要後端配置,並且由於是html5中的api,所以兼容性還不是很好,用的時候需要確定你的用戶在做決定。
2.1 HashRouter
具體實現代碼如下:
function HashRouter() { this.routes = {}; this.currentUrl = ''; }; HashRouter.prototype.init = function() { window.addEventListener( 'load', this.refresh.bind( this ), false ); window.addEventListener( 'hashchange', this.refresh.bind( this ), false ); }; HashRouter.prototype.refresh = function () { this.currentUrl = location.hash.slice( 1 ) || '/'; this.routes[ this.currentUrl ](); }; HashRouter.prototype.route = function( path, callback ) { this.routes[ path ] = callback || function(){}; };
1.首先定義一個HashRouter構造函數,currentUrl存放當前地址,routes存放歷史地址及其各自對應的回調函數。
2.route方法用於向hashRouter對象註冊路由地址和回調函數
3.refresh方法會在路由切換時執行刷新頁面
4.在路由註冊完成後會執行init方法,以監聽地址欄hash值的變化
2.2 BrowserRouter
上面使用的hash方法實現路由固然不錯,但是也是存在一定的問題的,就是太醜~ 如果在微信中使用,看不到url倒也無所謂,但是如果在一般的瀏覽器中使用就會遇到問題了。所以這裏使用 history api。具體代碼如下:
<script type="text/javascript"> function MyBrowserRouter() { this.currentRoute = ''; this.routes = {}; this.init(); } MyBrowserRouter.prototype.route = function( path, callback ) { this.routes[ path ] = function( type ) { if ( type === 1 ) { history.pushState( { path }, path, path ); } else if ( type === 2 ) { history.replaceState( { path }, path, path ); } callback(); } }; MyBrowserRouter.prototype.refresh = function( path, type ) { this.routes[ path ]( type ); }; MyBrowserRouter.prototype.init = function() { let links = document.querySelectorAll( '.history-link' ); window.addEventListener( 'load', () => { this.currentRoute = location.href.slice( location.href.indexOf( '/', 8 ) ); this.refresh( this.currentRoute, 2 ); } ); window.addEventListener( 'popstate', () => { this.currentRoute = history.state.path; this.refresh( this.currentRoute, 2 ); }, false ); for ( let i = 0; i < links.length; i++ ) { links[ i ].onclick = ( e ) => { e.preventDefault(); this.currentRoute = e.target.getAttribute( 'href' ); this.refresh( this.currentRoute, 2 ); } } }; </script>