單頁應用的原理從早起的根據url的hash變化,到根據H5的history的變化,實現無重新整理條件下的頁面重新渲染。那麼在單頁應用中是如何監聽url的變化呢,本文將總結一下,如何在單頁頁面中優雅的監聽url的變化。
- 單頁應用原理
- 監聽url中的hash變化
- 監聽通過history來改變url的事件
- replaceState和pushState行為的監聽
一、單頁應用原理
單頁應用的原理,在我們的上一篇文章中React-Router原始碼閱讀已經講的很詳細,這裡做一個簡單介紹。單頁應用使得頁面可以在無重新整理的條件下重新渲染,通過hash或者html5 Bom物件中的history可以做到改變url,但是不重新整理頁面。
(1)通過hash來實現單頁路由
早期的前端路由是通過hash來實現的:
因此可以通過hash來實現前端路由,從而實現無重新整理的效果。hash屬性位於location物件中,在當前頁面中,可以通過:
window.location.hash='edit'
複製程式碼複製程式碼
來實現改變當前url的hash值。執行上述的hash賦值後,頁面的url發生改變。
賦值前:http://localhost:3000 賦值後:http://localhost:3000/#edit
在url中多了以#結尾的hash值,但是賦值前後雖然頁面的hash值改變導致頁面完整的url發生了改變,但是頁面是不會重新整理的。
此外,除了可以通過window.location.hash來改變當前頁面的hash值外,還可以通過html的a標籤來實現:
<a href="#edit">edit</a>
複製程式碼複製程式碼
(2)通過history實現前端路由
HTML5的History介面,History物件是一個底層介面,不繼承於任何的介面。History介面允許我們操作瀏覽器會話歷史記錄。
History提供了一些屬性和方法。
History的屬性:
- History.length: 返回在會話歷史中有多少條記錄,包含了當前會話頁面。此外如果開啟一個新的Tab,那麼這個length的值為1
- History.state: 儲存了會出發popState事件的方法,所傳遞過來的屬性物件(後面會在pushState和replaceState方法中詳細的介紹)
History方法:
-
History.back(): 返回瀏覽器會話歷史中的上一頁,跟瀏覽器的回退按鈕功能相同
-
History.forward():指向瀏覽器會話歷史中的下一頁,跟瀏覽器的前進按鈕相同
-
History.go(): 可以跳轉到瀏覽器會話歷史中的指定的某一個記錄頁
-
History.pushState():pushState可以將給定的資料壓入到瀏覽器會話歷史棧中,該方法接收3個引數,物件,title和一串url。pushState後會改變當前頁面url,但是不會伴隨著重新整理
-
History.replaceState():replaceState將當前的會話頁面的url替換成指定的資料,replaceState後也會改變當前頁面的url,但是也不會重新整理頁面。
上面的方法中,pushState和repalce的相同點:
不同點:
(3)總結
通過改變hash值,或者history的repalceState和pushState都可以實現無重新整理的改變url。這樣還留有一個問題需要解決:
因為我們不僅要無重新整理的改變url,還要監聽到這個url改變的行為,根據該行為去重新渲染檢視。在下幾章中,重點介紹一下如何監聽url的改變。
二、監聽url中的hash變化
通過hash改變了url,會觸發hashchange事件,只要監聽hashchange事件,就能捕獲到通過hash改變url的行為。
window.onhashchange=function(event){
console.log(event);
}
//或者
window.addEventListener('hashchange',function(event){
console.log(event);
})
複製程式碼複製程式碼
當hash值改變時,輸出一個HashChangeEvent。該HashChangeEvent的具體值為:
{isTrusted: true, oldURL: "http://localhost:3000/", newURL: "http://localhost:3000/#teg", type: "hashchange".....}
複製程式碼複製程式碼
有了監聽事件,且改變hash頁面不重新整理,這樣我們就可以在監聽事件的回撥函式中,執行我們展示和隱藏不同UI顯示的功能,從而實現前端路由。
三、監聽通過history來改變url的事件
在上一章講到了通過History改變url有以下幾種方法:History.back()、History.forward()、History.go()、History.pushState()和History.replaceState()。
同時在history中還支援一個事件,該事件為popstate。第一想法就是如果popstate能夠監聽所有的history方法所導致的url變化,那麼就大功告成了。遺憾的是:
History.back()、History.forward()、History.go()事件是會觸發popstate事件的,但是History.pushState()和History.replaceState()不會觸發popstate事件。
如果是History.back(),History.forward()、History.go()那麼會觸發popstate事件,我們只需要:
window.addEventListener('popstate', function(event) {
console.log(event);
})
複製程式碼複製程式碼
就可以監聽到相應的行為,手動呼叫:
window.history.go();
window.history.back();
window.history.forward();
複製程式碼複製程式碼
都會觸發這個事件,此外,在瀏覽器中點選後退和前進按鈕也會觸發popstate事件,這個事件內容為:
PopStateEvent {isTrusted: true, state: null, type: "popstate", target: Window, currentTarget: Window, …}
複製程式碼複製程式碼
但是,History.pushState()和History.replaceState()不會觸發popstate事件,舉例來說:
window.addEventListener('popstate', function(event) {
console.log(event);
})
window.history.pushState({first:'first'}, "page 2", "/first"})
複製程式碼複製程式碼
上述例子中不會有任何的輸出,因為並沒有監聽的popstate事件的發生。
但是History.go和History.back()等,雖然可以觸發popstate事件,但是都會重新整理頁面,我們在單頁應用中使用的是replaceState和pushState,因此這裡還有一個等待解決的問題:
四、replaceState和pushState行為的監聽
在上面的例子中我們發現History.replaceState和pushState確實不會觸發popstate事件,那麼如何監聽這兩個行為呢。可以通過在方法裡面主動的去觸發popState事件。另一種就是在方法中建立一個新的全域性事件。
具體做法為:
var _wr = function(type) {
var orig = history[type];
return function() {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');
複製程式碼複製程式碼
這樣就建立了2個全新的事件,事件名為pushState和replaceState,我們就可以在全域性監聽:
window.addEventListener('replaceState', function(e) {
console.log('THEY DID IT AGAIN! replaceState 111111');
});
window.addEventListener('pushState', function(e) {
console.log('THEY DID IT AGAIN! pushState 2222222');
});
複製程式碼複製程式碼
這樣就可以監聽到pushState和replaceState行為。