2013-03-08 45 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);