2013-03-08 50 views
11

我想用Backbone.js使用TypeScript。它「起作用」,但是Backbone的get()和set()會丟失大部分的類型安全。我正在嘗試編寫一個可以恢復類型安全的幫助程序方法。事情是這樣的:Backbone和TypeScript,一次不幸的婚姻:構建一個類型安全的「get」?

我把這個在我的模型:

object() : IMyModel { 
    return attributes; // except I should use get(), not attributes, per documentation 
} 

這在消費: var myVar = this.model.object().MyProperty;

有了這個語法,我得到打字稿的知識myProperty的存在,並且是布爾,這真棒。但是,backbone.js docs告訴我直接使用get和set而不是屬性散列。那麼有沒有什麼神奇的Javascript方式來通過獲取和設置正確的方式來管理該對象的使用?

+0

沒有答案,但我發現了同樣的問題,並用一組不同的工具來管理數據和綁定(普通但強類型的TS對象+ Linq。 js + jsrender/jsviews(帶有可觀察類 - http://bit.ly/WyNbhn))。參考:http://linqjs.codeplex.com/ https://github.com/BorisMoore – JcFx 2013-03-08 16:10:02

+0

我想你可能需要等待TypeScript支持泛型以一種理智的方式做到這一點(v0。9) – 2013-03-08 17:29:31

+0

@RyanCavanaugh現在的答案是不被接受的,因爲像數組這樣更復雜的對象會崩潰,現在TypeScript支持泛型,也許你可以提供一個更新的答案? – parliament 2013-08-21 21:03:54

回答

16

我們正在大量使用TypeScript的骨幹,並提出了一種新穎的解決方案。
考慮下面的代碼:

interface IListItem { 
    Id: number; 
    Name: string; 
    Description: string; 
} 

class ListItem extends Backbone.Model implements IListItem { 
    get Id(): number { 
     return this.get('Id'); 
    } 
    set Id(value: number) { 
     this.set('Id', value); 
    } 
    set Name(value: string) { 
     this.set('Name', value); 
    } 
    get Name(): string { 
     return this.get('Name'); 
    } 
    set Description(value: string) { 
     this.set('Description', value); 
    } 
    get Description(): string { 
     return this.get('Description'); 
    } 

    constructor(input: IListItem) { 
     super(); 
     for (var key in input) { 
      if (key) { 
       //this.set(key, input[key]); 
       this[key] = input[key]; 
      } 
     } 
    } 
} 

注意接口定義模型的屬性,構造確保傳遞的任何物體都會有標識,名稱和描述特性。 for語句只需在每個屬性上調用backbone集。這樣,下面的測試將通過:

describe("SampleApp : tests : models : ListItem_tests.ts ",() => { 
    it("can construct a ListItem model",() => { 
     var listItem = new ListItem(
      { 
       Id: 1, 
       Name: "TestName", 
       Description: "TestDescription" 
      }); 
     expect(listItem.get("Id")).toEqual(1); 
     expect(listItem.get("Name")).toEqual("TestName"); 
     expect(listItem.get("Description")).toEqual("TestDescription"); 

     expect(listItem.Id).toEqual(1); 

     listItem.Id = 5; 
     expect(listItem.get("Id")).toEqual(5); 

     listItem.set("Id", 20); 
     expect(listItem.Id).toEqual(20); 
    }); 
}); 

更新: 我已經更新了代碼庫使用ES5 get和set語法,以及構造。基本上,您可以使用Backbone.get和.set作爲內部變量。

+1

也是listItem.Name =='TestName'?如果不是,那爲什麼定義這些道具?如果我使用listItem.set(「Name」,「NewName」),那麼我需要在兩個地方反映... – 2013-03-10 02:01:50

+0

好吧,所以我調整了使用ES6 getter和setter語法的代碼。讓我知道這是否適合你。玩得開心 – blorkfish 2013-03-12 13:35:31

+0

這個主題激勵我發佈一個關於強類型主幹模型的快速博客:http://blorkfish.wordpress.com/2013/03/20/typescript-strongly-typed-backbone-models/ – blorkfish 2013-03-20 14:31:02

9

我想出了以下使用泛型和ES5 getters/setters,構建了/u/blorkfish答案。

class TypedModel<t> extends Backbone.Model { 
    constructor(attributes?: t, options?: any) { 
     super(attributes, options); 

     var defaults = this.defaults(); 
     for (var key in defaults) { 
      var value = defaults[key]; 

      ((k: any) => { 
       Object.defineProperty(this, k, { 
        get:(): typeof value => { 
         return this.get(k); 
        }, 
        set: (value: any) => { 
         this.set(k, value); 
        }, 
        enumerable: true, 
        configurable: true 
       }); 
      })(key); 
     } 
    } 

    public defaults(): t { 
     throw new Error('You must implement this'); 
     return <t>{}; 
    } 
} 

注:Backbone.Model默認值是可選的,但由於我們用它來構建getter和setter,現在是強制性的。拋出的錯誤迫使你這樣做。也許我們可以想一個更好的方法?

,並使用它:

interface IFoo { 
    name: string; 
    bar?: number; 
} 

class FooModel extends TypedModel<IFoo> implements IFoo { 
    public name: string; 
    public bar: number; 

    public defaults(): IFoo { 
     return { 
      name: null, 
      bar: null 
     }; 
    } 
} 

var m = new FooModel(); 
m.name = 'Chris'; 
m.get('name'); // Chris 
m.set({name: 'Ben', bar: 12}); 
m.bar; // 12 
m.name; // Ben 

var m2 = new FooModel({name: 'Calvin'}); 
m2.name; // Calvin 

這是理想的稍微詳細,它要求你使用默認值,但它工作得很好。

+0

+1我喜歡它我沒有嘗試過,但它看起來像會起作用。但是,它將如何處理數組?例如'm.SomeArray.push(item)',這會觸發Backbone事件嗎?原生骨幹的方式非常醜陋,你必須得到模型,推動項目,設置模型。 ('var array = m.get(「SomeArray」); array.push(item); m.set(「SomeArray」,array);')。你的實施可以使這更自然嗎? – parliament 2013-12-16 22:52:19

+0

你的示例方法不起作用,因爲你的設置基本上沒有做任何事情。 array === m.get('SomeArray')因爲它們的對象引用是一樣的。你將不得不做一個沉默的unset然後一個集合,克隆數組並將其值設置爲新數組,或者手動觸發一個更改事件? – 2013-12-17 15:54:40

+0

無論哪種方式,我的實現只是調用骨幹獲取和設置調用,所以任何你需要做的解決方法都是一樣的。 – 2013-12-17 15:55:59

0

下面是使用裝飾的方式,創建一個基類是這樣的:

export class Model<TProps extends {}> extends Backbone.Model { 

    static Property(fieldName: string) { 
     return (target, member, descriptor) => { 
      descriptor.get = function() { 
       return this.get(fieldName); 
      }; 
      descriptor.set = function(value) { 
       this.set(fieldName, value); 
      }; 
     }; 
    } 

    attributes: TProps; 
} 

然後創建自己的類這樣的:

class User extends Model<{id: string, email: string}> { 
    @Model.Property('id')  set Id(): string { return null; } 
    @Model.Property('email')  set Email(): string { return null; } 
} 

並使用它:

var user = new User; 
user.Email = '[email protected]'; 
console.log(user.Email);