1. 程式人生 > >在單頁應用中,如何優雅的監聽url的變化

在單頁應用中,如何優雅的監聽url的變化


  單頁應用的原理從早起的根據url的hash變化,到根據H5的history的變化,實現無重新整理條件下的頁面重新渲染。那麼在單頁應用中是如何監聽url的變化呢,本文將總結一下,如何在單頁頁面中優雅的監聽url的變化。

  • 單頁應用原理
  • 監聽url中的hash變化
  • 監聽通過history來改變url的事件
  • replaceState和pushState行為的監聽

原文在我的部落格中:github.com/fortheallli…

歡迎star

一、單頁應用原理

  單頁應用的原理,在我們的上一篇文章中React-Router原始碼閱讀已經講的很詳細,這裡做一個簡單介紹。單頁應用使得頁面可以在無重新整理的條件下重新渲染,通過hash或者html5 Bom物件中的history可以做到改變url,但是不重新整理頁面。

(1)通過hash來實現單頁路由

  早期的前端路由是通過hash來實現的:

  改變url的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的相同點:

就是都會改變當前頁面顯示的url,但都不會重新整理頁面。

不同點:

pushState是壓入瀏覽器的會話歷史棧中,會使得History.length加1,而replaceState是替換當前的這條會話歷史,因此不會增加History.length.

(3)總結

  通過改變hash值,或者history的repalceState和pushState都可以實現無重新整理的改變url。這樣還留有一個問題需要解決:

  如何監聽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行為

四、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行為。

參考文章:stackoverflow.com/questions/4…