天天看點

《BackboneJS架構的技巧及模式》(4)完結篇

這一條也經常會對那些Backbone.js新手造成困擾。要想在調用.save()的時候讓模型發送一個HTTP PUT請求,你的模型需要有一個ID屬性集。記得HTTP PUT謂詞是設計用做更新的吧,是以發送一PUT請求,你的模型需要有一個ID。在理想情況,你的所有模型都有一名為ID的完美ID屬性,但是你從端點接收到的JSON資料或許并不總是有完美命名的ID。

是以,如果你需要更新一個模型,在儲存前確定模型上帶有ID。Backbone.js從0.5版開始允許使用idAttribute來修改模型ID屬性的名稱,如果端點傳回的不是名為id的ID屬性名時。

如果使用的Backbone.js版本低于0.5,建議你修改模型或集合的parse函數,以便将期望的ID屬性映射到屬性ID。這裡有一個快速上手的例子,說明了應怎樣修改parse函數來做到這一點。我們假設你有一個cars的集合,它的ID屬性名是carID。

parse: function(response) {  

    _.each(response.cars, function(car, i) {  

        // map the returned ID of carID to the correct attribute ID  

        response.cars[i].id = response.cars[i].carID;  

    });  

    return response;  

},  

有時你會發現模型或集合需要在頁面加載時進行初始化。許多關于Backbone.js模式的文章,例如Rico Sta Cruz的“Backbone模式”和Katz的“避免常見的Backbone.js陷阱”,讨論了這種模式。這種模式實作很容易,隻需在頁面中内聯一段腳本,通過你選擇的服務端語言,将單個模型屬性或者JSON形式的資料呈現出來。例如,在Rails語言中,我采用下面的一種方法:

// a single attribute  

var model = new Model({  

    hello:   

});  

// or to have json  

var model = new Model();  

應用此模式可以通過“立即”渲染頁面,以改善你網站的搜尋引擎排名,而且它也可通過限制應用初始化HTTP請求的方式,大幅縮短應用啟動與運作所需的時間。

很多時候,你會想知道是哪個模型屬性驗證失敗了。例如,如果你有一個極其複雜的表單字段,你或許想知道哪一個模型屬性驗證失敗了,以便于你可以将該屬性對應的輸入字段進行高亮顯示。不幸的是,警告視圖到底是哪個模型屬性驗證失敗并沒有直接內建于Backbone.js,但是你可以用其他不同的模式去處理這個問題。

一個通知視圖是哪個模型屬性驗證失敗的模式是,傳回一個對象,其中包含某種标志,它詳細的記錄了驗證失敗的屬性,就像下面這樣:

// Inside your model  

validate: function(attrs) {  

    var errors = [];  

    if(attrs.a 

        errors.push({  

            'message': 'Form field a is messed up!',  

            'class': 'a'  

        });  

    }  

    if(attrs.b 

            'message': 'Form field b is messed up!',  

            'class': 'b'  

    if(errors.length) {  

        return errors;  

}  

// Inside your view  

this.model.on('invalid’, function(model, errors) {  

    _.each(errors, function(error, i) {  

        $(‘.’ + error.class).addClass('error');  

        alert(error.message);  

此模式的優點在于,你可以在一個地方處理所有無效的消息。缺點在于,如果你用不同的invalid屬性處理無效屬性,那麼你的invalid方法裡面可能會有非常龐大的switch語句或if語句。

我的朋友Derick Bailey推薦了一個可替代的模式,就是為每個模型屬性觸發自定義的errors事件。它允許你的視圖能夠針對單獨的屬性綁定特定的error事件:

            this.trigger(‘invalid:a’, 'Form field a is messed up!', this);  

            this.trigger(‘invalid:b’, 'Form field b is messed up!', this);  

this.model.on('invalid:a’, function(error) {  

        $(‘a’).addClass('error');  

        alert(error);  

this.model.on('invalid:b’, function(error) {  

        $(‘b’).addClass('error');  

此模式的優點在于,你的視圖綁定的error類型是清晰的,并且對于每一種error屬性,可以有特定的指令,它可以使得視圖方面的代碼很清晰,使之更易于維護。此模式的缺點在于,視圖的代碼可能會變得極為膨脹。

這兩種模式都各有利弊,你應該考慮清楚哪個模式對你的應用案例是最優的。如果你按照同樣的方式處理所有失敗的驗證,那麼第一個方法可能是最好的;如果你對每個模型屬性有特定的UI變化,那麼後一種方法會更好。

如果終端的模型或集合收到了無效的JSON,盡管終端傳回了HTTP狀态碼200,模型或集合還是會觸發error事件。這種問題經常發生于開發階段在本地模拟JSON資料造成的。那麼,有個好方法是抛出經過 JSON 驗證器驗證了的模拟JSON資料檔案。或者使用IDE的插件來及時捕獲格式錯誤的JSON資料。

這能夠節省開發時間以及建立統一的模式來處理和可視化錯誤消息,而且它可以改善整個的使用者體驗。我之前開發的每一個Backbone.js應用中,我都會建立一個通用的警告視圖:

var AlertView = Backbone.View.extend({  

    set: function(typeOfError, message) {  

        var alert = $(‘.in-page-alert’).length ? $(‘.in-page-alert’): $(‘.body-alert’);  

        alert  

            .removeClass(‘error success warning’)  

            .addClass(typeOfError)  

            .html(message)  

            .fadeIn()  

            .delay(5000)  

            .fadeOut();  

上面代碼首先會檢查視圖代碼中是否存在指定in-page-alert視圖的DIV元素。如果沒有,則接着檢視通用的body-alert的DIV元素,它可以在布局中進行聲明。這可以傳遞一緻性的錯誤資訊給使用者,而且一旦你忘記指定in-page-alert視圖的DIV元素,它會有一個有用的回報。下面的模式簡化了怎樣在試圖中處理錯誤資訊:

var alert = new AlertView();  

this.model.on('error', function(model, error) {  

    alert.set('TYPE-OF-ERROR', error);  

如果你正在開發一個單頁面應用,切記要更新每個頁面的标題。我寫過一個簡單的插件(Backbone.js Router Title Helper),它以簡單、優雅的格式擴充了 backbone.js router 的功能。它通過一個 Map 對象來控制路由,鍵來代表路由函數的名字,值則映射到頁面的标題。

Backbone.Router = Backbone.Router.extend({  

    initialize: function(options){  

        var that = this;  

        this.on('route', function(router, route, params) {  

            if(that.titles) {  

                if(that.titles[router]) document.title = that.titles[router];  

                else if(that.titles.default) document.title = that.titles.default;  

                else throw 'Backbone.js Router Title Helper: No title found for route:' + router + ' and no default route specified.';  

            }  

當談論單頁應用時,緩存對象的模式會經常用到!下面的例子簡明扼要:

// Inside a router  

initialize: function() {  

    this.cached = {  

        view: undefined,  

        model: undefined  

index: function(parameter) {  

    this.cached.model = this.cached.model || new Model({  

        parameter: parameter  

    this.cached.view = this.cached.view || new View({  

        model: this.cached.model  

此模式可以加速你的應用程式,因為你不用重新初始化Backbone.js對象。然而,它會過多地消耗記憶體;是以,緩存的對象應該是在整個應用程式中使用的。如果以前你用過Backbone.js開發過應用,也許你會問你自己,“ 我要重取資料該怎麼做?”你可以每次在如下路徑中觸發後重取資料:

    this.cached.model.fetch();  

當應用程式從端點(比如收件箱)必須取回最新的資料,上面的模式就可以工作。當然,如果待取回的資料時依賴于應用程式的狀态(假設這個狀态是通過URL和參數來決定的),那麼即使應用程式的狀态為發生改變,也應該重新取回資料。更好的解決方案是隻在應用程式的參數發生變化時重新取回資料:

        parameter:parameter  

    this.cached.model.set('parameter', parameter);  

// Inside of the model  

    this.on("change:parameter", this.fetchData, this);  

我是文檔注釋和JSDoc的超級粉絲。我用JSDoc對所有的Backbone類添加了文檔注釋:

var Thing = Backbone.View.extend(/** @lends Thing.prototype */{  

    /** @class Thing 

     * @author Phillip Whisenhunt 

     * @augments Backbone.View 

     * @contructs Thing object */  

    initialize() {},  

    /** Gets data by ID from the thing. If the thing doesn't have data based on the ID, an empty string is returned. 

     * @param {String} id The id of get data for. 

     * @return {String} The data. */  

    getDataById: function(id) {}  

如果你使用上面的方法對Backbone類添加文檔注釋,那麼你可以生成優美的文檔,文檔包含了所有的類和函數及參數。確定初始化函數應作為第一個聲明的函數,它可以幫助我們生成JSDoc文檔。如果你想看使用JSDoc的項目例子,這裡有:

<a href="https://github.com/homeaway/calendar-widget" target="_blank">https://github.com/homeaway/calendar-widget</a>

我認為如果你使用Backbone.js開發項目,那麼你應遵循測試驅動開發TDD。我第一次用Jasmine.js建立模型和集合時遵循TDD進行單元測試,但失敗了。一旦寫下單元測試并且失敗,我會對整個模型和集合進行重寫。

通過這一點,我的所有Jasmine測試都通過了,而且我有信心我的模型及集合會和我期望的一樣工作。自從我遵循TDD,我的視圖層非常容易編寫而且非常簡單。當你開始用TDD時,你的開發速度當然會很慢;但是一但你的腦海裡一直想着TDD,你的程式設計效率和品質會顯著提高。

繼續閱讀