天天看點

《BackboneJS架構的技巧及模式》(3)《BackboneJS架構的技巧及模式》(3)

《BackboneJS架構的技巧及模式》(3)

本文緊接第二部分:

《BackboneJS架構的技巧及模式(2)》

作者:chszs,轉載需注明。部落格首頁:

http://blog.csdn.net/chszs

四、頁面部分重新整理

當第一次使用Backbone.js開發應用時,典型的視圖結構是像這樣的:

var View = Backbone.View.extend({
    initialize: function(options) {
        this.model.on('change', this.render, this);
    },

    template: _.template($(‘#template’).html()),

    render: function() {
        this.$el.html(template(this.model.toJSON());
        $(‘#a’, this.$el).html(this.model.get(‘a’));
        $(‘#b’, this.$el).html(this.model.get(‘b’));
    }
});
           

在這裡,任何對模型的改變都會觸發整個視圖的完全重新整理。我第一次用Backbone.js開發時,我是此模式的實踐者。但随着視圖代碼的增長,我很快意識到,這種方式不利于維護或優化,因為模型的任一屬性發生改變時,都會觸發視圖的完全重新整理。

當我遇到這個問題,我快速用Google搜尋了一下,看别人是怎麼做的。結果找到了Ian Storm Taylor的部落格,“分解Backbone.js渲染方法”,他在其中描述了在模型中監聽單個的屬性變化,然後僅僅重新渲染相對于視圖中屬性變化的那部分。Taylor也描述了傳回對象的引用,以便單個渲染函數可以很容易的連結在一起。上面的例子現在就可以修改的易于維護、性能也更優。因為我們隻需做視圖的部分重新整理。

var View = Backbone.View.extend({
    initialize: function(options) {
        this.model.on('change:a', this.renderA, this);
        this.model.on('change:b', this.renderB, this);
    },

    renderA: function() {
        $(‘#a’, this.$el).html(this.model.get(‘a’));
        return this;
    },

    renderB: function() {
        $(‘#b’, this.$el).html(this.model.get(‘b’));
        return this;
    },

    render: function() {
        this
            .renderA()
            .renderB();
    }
});
           

我要提醒一下:有不少插件,比如Backbone.StickIt和Backbone.ModelBinder,提供了模型屬性與視圖元素的鍵值綁定,這會讓你省去編寫很多樣闆代碼的時間,是以,如果你需要實作比較複雜的表單字段,那麼可以看看這些插件。

五、保持模型與視圖無關

正如 Jeremy Ashkenas 在 Backbone.js的GitHub問題中所指出的,Backbone.js并沒有實施資料模型與視圖層之間的真正分隔,除非模型不是引用視圖建立的。因為Backbone.js并沒有實施任何關注點分隔,是以你應該将其分離嗎?我和許多Backbone.js開發人員,如Oz Katz和Dayal,都相信答案毫無疑問是yes:模型與集合,也就是資料層,應該徹底的與綁定它們的視圖無關,保持一個清晰的關注點分離。如果你沒有遵循關注點分離,你的代碼庫會很快變成意大利面條式的代碼,而實際上是沒有人喜歡意大利面條式的代碼。

保持模型與視圖無關将會幫助你預防意大利面條式的代碼,而沒有人喜歡意大利面條式的代碼!

保持資料層完全與視圖層無關,這會使你建立出更具子產品化、可重用和可維護的代碼庫。你可以很容易地在應用程式各個地方重用和擴充模型與集合,而無需考慮它們所綁定的視圖。遵循此模式使對項目不熟悉的新手也能迅速深入到代碼庫,因為他們會确切地知道哪裡發生了渲染,業務邏輯存在于哪裡。

這個模式也實作了單一職責原則——它規定了每個類應該具有單個職責,而且它的職責應該封裝在類中,因為模型與集合負責處理資料,而視圖則負責處理渲染。

六、路由的參數映射

此模式的最佳示範是了解整個例子。比如說對搜尋頁的結果進行排序,搜尋頁允許使用者添加兩個不同的過濾類型,foo和bar,每個類型代表不同的過濾規則。那麼,你的URL結構應該是這樣:

'search/:foo'
'search/:bar'
'search/:foo/:bar'
           

現在,所有路由都使用相同的視圖和模型,這樣大多數人都喜歡用同一個函數search()來實作。然而,你要是檢查過Backbone.js代碼的話,你會發現它裡面沒有任何參數映射的排序;這些參數隻是從左至右依次傳入函數。這樣,為了能統一使用同一函數,你要停止建立不同的函數并正确地把參數映射到search()。

routes: {
    'search/:foo': 'searchFoo',
    'search/:bar': 'searchBar',
    'search/:foo/:bar': 'search'
},

search: function(foo, bar) {    
},

// I know this function will actually still map correctly, but for explanatory purposes, it's left in.
searchFoo: function(foo) {
    this.search(foo, undefined);
},

searchBar: function(bar) {
    this.search(undefined, bar);
},
           

如你所想,此模式可以使路由功能快速膨脹。當我第一次遇到這個問題時,我試圖建立一些正規表達式解析實際的函數定義來實作參數的映射,當然這是可以工作的——但也是受限制的。是以,我放棄了我的這個想法(或許我仍然可以通過BackboneJS的插件來解決)。我進入GitHub的Issues頁面,其中Ashkenas建議應該讓所有的參數都映射到search函數。 

上面的代碼現在可修改為如下的維護性更強的代碼:

routes: {
    'base/:foo': 'search',
    'base/:bar': 'search',
    'base/:foo/:bar': 'search'
},

search: function() {
    var foo, bar, i;

    for(i = arguments.length - 1; i >= 0; i--) {

        if(arguments[i] === 'something to determine foo') {
            foo = arguments[i];
            continue;
        }
        else if(arguments[i] === 'something to determine bar') {
            bar = arguments[i];
            continue;
        }
    }
},
           

此模式可以顯著減少路由的過度膨脹。但是,需注意到如果它不能識别參數,則它不會工作。比如,如果你有兩個帶ID的參數,如模式XXXX-XXXX,那麼你不能區分哪個ID是對哪個參數的響應。

七、model.fetch() 不會清除你的模型

通常它會對那些Backbone.js新手造成困擾:model.fetch()不會清除你的模型,而是繼承模型的屬性。是以,假如模型具有屬性x、y和z,你取回y和z,那麼屬性x仍然是模型中的那個x,而屬性y和z會被更新。下面的例子說明了這一點:

var Model = Backbone.Model.extend({
    defaults: {
        x: 1,
        y: 1,
        z: 1
    }
});
var model = new Model();
/* model.attributes yields
{
    x: 1,
    y: 1,
    z: 1
} */
model.fetch();
/* let’s assume that the endpoint returns this
{
    y: 2,
    z: 2,
} */
/* model.attributes now yields
{
    x: 1,
    y: 2,
    z: 2
} */
           

繼續閱讀