使用backbone的history管理SPA應用的url

本文介紹如何使用backbone的history模塊實現SPA應用裏面的URL管理。SPA應用的核心在於使用無刷新的方式更改url,從而引起頁面內容的改變。從實現上來看,url的管理和頁面內容的管理是其中的兩個難點。就url的管理而言,主要有如下三方面的要求:javascript

1)對於要採用單頁跳轉的連接,不能有頁面刷新;
2)瀏覽器的前進和後退,都能像多頁應用那樣,顯示以前訪問地址對應的內容;
3)應用處於任何一個單頁連接地址時,當用戶刷新,依然能初始化顯示該地址對應的內容。html

假如要本身來實現一個可以知足以上三方面要求的功能,思路有2種。java

第一種是利用錨點連接及hashchange,將全部單頁連接地址所有配置成錨點連接的形式,而後在hashchange事件裏面,根據頁面當前的錨點值,執行不一樣的回調函數,用於更改頁面內容。這個思路在上一篇博客中《理解瀏覽器歷史記錄(2)-hashchange、pushState》給出了一個簡單的實現(demo),代碼雖然比較簡陋,可是也說明思路是可行的。git

第二種是利用pushState,詳細步驟以下:github

1)在點擊連接的時候,若是這個連接是單頁形式的連接,可經過pushState或者replaceState的方式來改變url;因爲pushState跟replaceState並不會觸發popstate事件,因此在必要的條件下,還得在調用完pushState和replaceState調用完後,手動調用相應的回調函數;
2)監聽瀏覽器的popstate事件,這樣就能響應瀏覽器前進後退的操做,而後根據操做後的頁面地址找到對應的回調函數執行;
3)頁面初始化時,直接根據當前地址執行對應的回調函數便可。chrome

上次也沒有給出簡單使用pushState實現SPA的例子,此次補上,功能與hashchange那個相似,就是寫法稍微有點不一樣而已。demo地址(不可測刷新操做,若是使用pushState,單頁地址刷新會報404,需在後臺處理才能解決,此處畢竟只是個靜態頁):api

http://liuyunzhuge.github.io/blog/pushState/demo7.html瀏覽器

比較起來,用hashchange作spa的方法要簡單一些,並且兼容性更好,加上mdn上給出的用定時器來模擬hashchange的方法,基本上用它是能夠實現全瀏覽器兼容的SPA應用了。不過hashchange相比pushstate也有一些大的弊端:
一是url對本身不夠友好,原本在多頁應用裏面,url可能都是絕對路徑或者相對路徑開頭的形式,若是全換成#hash的形式,顯然違背本身多年的使用習慣;
二是url對搜索引擎不夠友好。
因此要是可以把hashchange,pushstate結合起來實現SPA裏面的url管理,顯然這個事情就變得很完美了。事實上已經有不少的所謂的路由框架作到這一點,我也沒有去研究別的,加上一直對backbone這個框架的評價不錯,因此就琢磨着怎麼用它實現我所須要的SPA的url管理了。框架

下面的內容就是介紹如何使用backbone的history模塊來實現spa的url管理。因爲只是介紹方法,因此對相關的api的使用不會一一進行說明。感興趣地能夠查閱backbone的官方文檔,主要是Router和History模塊的部分。要是想詳細瞭解瀏覽器如何管理歷史記錄,以及hashchange和pushstate使用要點的話,也能夠看看我前面兩篇博客寫的一些基礎知識總結,它們對於我理解Backbone的history模塊仍是頗有幫助。函數

理解瀏覽器歷史記錄(2)-hashchange、pushState
理解瀏覽器的歷史記錄

history使用介紹

按照官方文檔的說明,backbone的history模塊能夠實現從pushstate到hashchange到url直接跳轉,這三種方式的優雅降級。爲了驗證這一點,我專門作了幾個demo。

先來看demo1:http://liuyunzhuge.github.io/blog/backbone_router/demo1.html

在正式介紹這個demo前,先要了解一點,網頁裏的a標籤的href屬性有多種形式:
1)http(s)形式的,如href="/index",href="../index",href="http://www.baidu.com",href="https://www.baidu.com"
2)錨點形式,如href="#container"
3)腳本形式,如href="javascript:;"
4)撥打電話,如href="tel:95555"
5)郵件連接,如href="mailto:xxx@yyy.com"
6)其它自定義的形式等
a標籤被點擊時,瀏覽器都會有默認的行爲,好比http(s)形式的連接會引起網頁的跳轉;錨點形式的連接會引起hashchange以及錨點內容的定位。在單頁應用裏面,大部分的http(s)形式的連接都要採用單頁形式跳轉,但也不是全部的連接都要使用單頁形式跳轉。在管理url的時候,要區分出哪些連接須要用單頁形式的跳轉。backbone的history模塊能夠解決的是url管理問題,可是不會解決如何區分哪些連接須要單頁形式跳轉的問題。固然實際上解決後面這個問題也很簡單,後面的部分會詳細說明,這裏只是說明這個實現的要點。

接下來在chrome中新開一個選項卡,打開上面的demo1連接以及控制檯,可看到下面相似的界面:

image

作一些測試:點擊列表連接;點擊詳情連接;點擊關於連接;點擊瀏覽器倒退;點擊瀏覽器倒退;點擊瀏覽器倒退;點擊瀏覽器前進;點擊瀏覽器前進;點擊瀏覽器前進。觀察瀏覽器地址欄以及控制檯的變化(因爲沒有考慮在url更改的時候去更改頁面內容,因此只能用控制檯的打印的方式來簡單代替一下url變化的頁面內容更改的邏輯)

好比點擊列表連接後,頁面狀態應該是這樣的:

image

在全部的操做中,不難發現整個頁面都是知足SPA要求的(固然,這個demo不能作刷新操做測試,道理同上)。接下來看看這個demo的一些要點:

首先爲了解決前面的提出的區分連接是否要作單頁跳轉的問題,在須要單頁跳轉的a元素上增長了一個spa的屬性:

<li><a spa href="/">首頁</a></li>
<li><a spa href="/list">列表</a></li>
<li><a spa href="/detail/1">詳情</a></li>
<li><a spa href="/about">關於</a></li>

demo裏面除了頂部的四個連接有加spa的標識,還給出了一些普通連接,點擊以後會按照默認行爲進行交互,用來對比單頁連接的行爲。觀察這個html結構,也不難發現,儘管這些連接都是單頁的,可是它們的href的形式還跟多頁應用裏的書寫方式同樣。

而後考慮如何自定義這些單頁連接的行爲。個人實現是經過事件委託的方式,在document對象上代理這些a元素的點擊事件:

$(document).on('click', 'a[spa]', function (e) {
    var $a = $(this);
    var href = $.trim($a.attr('href'));

    var options = {
        trigger: $a.data('trigger') !== false,//默認點擊連接後都要自動觸發回調
        replace: Backbone.history.getFragment(href) == Backbone.history.fragment//若是連接地址與當前地址匹配就採用replace的方式
    };

    Backbone.history.navigate(href, options);

    e.preventDefault();
});

而後在這個代碼的內部,取消瀏覽器的默認行爲,而後很簡單的調用Backbone.history.navigate方法來完成url的跳轉,它會根據瀏覽器對pushState的支持狀況以及history模塊初始化的方式,來判斷是用pushstate api更改url仍是經過hash來更改url。這個方法的第二參數,是一個選項配置對象,trigger屬性決定是否在url跳轉完畢後,自動觸發相應的回調函數;replace屬性,決定了是否在瀏覽器歷史記錄棧中建立新的條目。考慮到這裏是全局定義單頁連接的行爲,replace屬性是否爲true徹底根據連接的地址是否與當前url匹配決定的,若是匹配那麼replace就是true,意味着連接是重複點擊,不須要建立新的歷史記錄條目,不然就是false,意味着當前連接點擊後須要跳轉到一個新的地址,因此須要建立新的歷史記錄條目;trigger屬性,除非顯示地在a元素上加了data-trigger=false,不然它傳遞到navigate始終是true。

trigger屬性設置爲true是比較關鍵的,尤爲是當pushstate被使用的時候,navigate內部僅僅只是用pushstate api改變了頁面地址,因此要手工地去觸發相應的回調函數執行。

接着在demo裏面還有下面一段簡單的代碼,用到了Backbone.Router模塊,用來完成url規則與回調函數的定義:

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'index',
        'index': 'index',
        'list': 'renderList',
        'detail/:id': 'renderDetail',
        '*error': 'renderError'
    },
    index: function () {
        console.log('首頁action');
    },
    renderList: function () {
        console.log('列表action');
    },
    renderDetail: function (id) {
        console.log('詳情action, 詳情id爲: ' + id);
    },
    renderError: function (error) {
        console.log('URL錯誤, 錯誤信息: ' + error);
    }
});

var router = new AppRouter();

Router模塊是一個很簡單的模塊,它就是一個url規則與回調函數的映射表,被History模塊的實例所引用,當頁面地址匹配到了Router裏面定義的規則時,相應的回調函數就會執行。上面的用法已經比較全了,實際項目中,能夠有多個AppRouter這樣的模塊定義,而後各自初始化一個Router實例,這樣就能實現大量的url規則進行拆分管理。

最後經過下面的方式,來初始化啓用了pushstate管理的history模塊:

Backbone.history.start({pushState: true, root: ROOT_BASE + '/demo1.html/'});

root的做用能夠查看backbone官方文檔瞭解

以上就是本文要介紹的最重要的內容,它是一種比較簡潔的進行spa的url管理的方法,儘管可能還有一些場景沒有考慮到,可是按照以上思路,即便碰到一些使用不佳的場景,咱們也可以想辦法解決。

回到本部分開頭的內容,來看看backbone是否真能作到url管理的優雅降級。爲了驗證這一點,須要模擬另外兩種場景,分別是不支持pushState的場景和不使用pushState&hashchange的場景。

demo2:http://liuyunzhuge.github.io/blog/backbone_router/demo2.html

demo2與demo1的區別僅僅在於最後面初始化history模塊的方式,demo1是這樣的:

Backbone.history.start({pushState: true, root: ROOT_BASE + '/demo1.html/'});

demo2是這樣的:

Backbone.history.start({root: ROOT_BASE + '/demo2.html/'});

demo2的初始化代碼,沒有傳遞pushState屬性,在backbone內部會被認爲不須要使用pushstate,如今電腦上安的瀏覽器基本上都支持pushstate,因此在不更改backbone源碼的前提下,只能採起這種形式來模擬一種pushstate不支持,降級到hashchange處理的場景。

好在這個demo裏面,其它的代碼形式都沒有改變,最主要是單頁連接的地址仍是原來的那個形式:

<li><a spa href="/">首頁</a></li>
<li><a spa href="/list">列表</a></li>
<li><a spa href="/detail/1">詳情</a></li>
<li><a spa href="/about">關於</a></li>

接下來在chrome中新開一個選項卡,打開demo2的連接,按照demo1的測試操做,作一下測試:點擊列表連接;點擊詳情連接;點擊關於連接;點擊瀏覽器倒退;點擊瀏覽器倒退;點擊瀏覽器倒退;點擊瀏覽器前進;點擊瀏覽器前進;點擊瀏覽器前進。觀察地址欄及控制檯的變化。

最後會發現,控制檯的打印狀況跟demo1是一致的。可是瀏覽器的地址再也不是demo1那種形式的地址,好比點擊列表連接後,頁面地址變成了這種hash形式:

image

說明這個場景下的navigate方法,實際上是經過改變hash來實現的。而後瀏覽器前進後退的時候也應該是經過hashchange事件來實現的。最後這個demo能夠證實,backbone的history模塊,確實能夠完美地降級到hashchange來管理url,並且這個變化對於咱們的業務代碼來講是透明的。

demo3http://liuyunzhuge.github.io/blog/backbone_router/demo3.html

這個demo用來模擬連hashchange都沒法使用的場景。它與demo1的區別也僅僅在於history模塊初始化的方式,它的是:

Backbone.history.start({hashChange: false, root: ROOT_BASE + '/demo3.html/'});

經過顯示的配置hashChange爲false,告訴backbone不使用hashchange管理url。在這個場景下backbone.history.navigate方法,會直接進行url跳轉,而不會用到pushstate以及hash。正是如此,navigate最後並不會觸發url的回調函數,畢竟頁面已經重載了。可是backbone能作到的就是,當頁面初始化的時候,與頁面地址匹配的回調函數仍是會被執行,這也就保證了它在這種場景下,url地址與頁面內容的一致性。

不過這個demo3打開後,點擊那些「單頁連接」,最後都會報404,畢竟它只是用來模擬這個特殊狀況而已。可是從它start方法的源碼來看,backbone確實能作到在頁面初始化的時候執行與它對應的回調。經過這個demo,最後徹底證實了backbone在url管理時的兼容程度,確實很全面。實際工做中,能夠考慮僅借鑑demo1的方法便可。

最後,還要再補充一下的就是若是是經過demo1那種方式初始化History模塊,那麼demo3這個場景應該在絕大部分瀏覽器都不會出現。由於即便在那些不支持hashchange的瀏覽器上,backbone內部也經過定時器加iframe的方式,來模擬hashchange。也就是說若是不手動把hashchange配置爲false的話,backbone總會有一個模擬的hashchange做爲備用。這樣它就能讓你的應用始終保持SPA的狀態了。要想了解backbone內部管理url的細節,能夠僅看backbone源碼部分的router模塊和history模塊,最主要的就是history模塊的start和navigate方法,這兩個方法是url管理初始化和跳轉控制的核心,從中也能學到一些關於location使用的技巧。

小結

這篇文章的目的就是爲了介紹spa裏面url管理的思路以及backbone來管理url的作法。因爲我到目前爲止,還沒作過單頁的應用,因此這裏面有的方法或者觀點不必定徹底正確,但願有經驗的朋友看到以後能幫忙指正,感謝~下一篇介紹如何管理SPA裏面的頁面內容,方法正在琢磨中…

相關文章
相關標籤/搜索