2012-10-04 51 views
6

我正在玩TypeScript,我有几个functional mixinsEventableSettable,我想混入一个Model类(假装它像Backbone.js模型):上述TypeScript中的Mixins

function asSettable() { 
    this.get = function(key: string) { 
    return this[key]; 
    }; 
    this.set = function(key: string, value) { 
    this[key] = value; 
    return this; 
    }; 
} 

function asEventable() { 
    this.on = function(name: string, callback) { 
    this._events = this._events || {}; 
    this._events[name] = callback; 
    }; 
    this.trigger = function(name: string) { 
    this._events[name].call(this); 
    } 
} 

class Model { 
    constructor (properties = {}) { 
    }; 
} 

asSettable.call(Model.prototype); 
asEventable.call(Model.prototype); 

的代码工作正常,但如果我试图用的混合式方法,如(new Model()).set('foo', 'bar')一个不会编译。

我可以解决此通过

  1. 添加interface声明的混入
  2. 宣布在Model声明

虚拟get/set/on/trigger方法有没有干净的方式围绕虚拟声明?

+0

可能相关的解决方案,在[微软/打字稿#2919]来解决这个(https://github.com/Microsoft/TypeScript/issues/2919#issuecomment-173384825) – mucaho

回答

12

下面是使用interfacesstatic create()方法处理mixin的一种方法。接口支持多重继承,因此您无需重新定义您的mixin的interfacesstatic create()方法负责将Model()的实例作为IModel(需要<any>强制转换以禁止编译器警告)。需要复制IModel上的Model的所有成员定义,但这看起来像是在当前版本的TypeScript中实现所需内容的最简单方法。

编辑:我已经确定了一个稍微简单的方法来支持mixin,甚至创建了一个帮助类来定义它们。详情可查询over here

function asSettable() { 
    this.get = function(key: string) { 
    return this[key]; 
    }; 
    this.set = function(key: string, value) { 
    this[key] = value; 
    return this; 
    }; 
} 

function asEventable() { 
    this.on = function(name: string, callback) { 
    this._events = this._events || {}; 
    this._events[name] = callback; 
    }; 
    this.trigger = function(name: string) { 
    this._events[name].call(this); 
    } 
} 

class Model { 
    constructor (properties = {}) { 
    }; 

    static create(): IModel { 
     return <any>new Model(); 
    } 
} 

asSettable.call(Model.prototype); 
asEventable.call(Model.prototype); 

interface ISettable { 
    get(key: string); 
    set(key: string, value); 
} 

interface IEvents { 
    on(name: string, callback); 
    trigger(name: string); 
} 

interface IModel extends ISettable, IEvents { 
} 


var x = Model.create(); 
x.set('foo', 'bar'); 
+5

正要张贴此。 TypeScript确实应该扩展到支持类的混合,因为很多JS库目前都使用它(例如Backbone.js)。 –

+2

+1需要部分类和/或mixins –

+0

自ts1.4以来,你可以使用“ISettable&IEvents”而不是“IModel” – mif

3

最干净的方式做到这一点,althought它仍然需要双类型声明,是定义混入作为一个模块:

module Mixin { 
    export function on(test) { 
     alert(test); 
    } 
}; 

class TestMixin implements Mixin { 
    on: (test) => void; 
}; 


var mixed = _.extend(new TestMixin(), Mixin); // Or manually copy properties 
mixed.on("hi"); 

使用接口的替代方法是用类来破解它(虽然由于多重继承,你需要创建一个通用接口的混入):正如我在史蒂芬的答案评论

var _:any; 
var __mixes_in = _.extend; // Lookup underscore.js' extend-metod. Simply copies properties from a to b 

class asSettable { 
    getx(key:string) { // renamed because of token-clash in asEventAndSettable 
     return this[key]; 
    } 
    setx(key:string, value) { 
     this[key] = value; 
     return this; 
    } 
} 

class asEventable { 
    _events: any; 
    on(name:string, callback) { 
     this._events = this._events || {}; 
     this._events[name] = callback; 
    } 
    trigger(name:string) { 
     this._events[name].call(this); 
    } 
} 

class asEventAndSettable { 
    // Substitute these for real type definitions 
    on:any; 
    trigger:any; 
    getx: any; 
    setx: any; 
} 

class Model extends asEventAndSettable { 
    /// ... 
} 

var m = __mixes_in(new Model(), asEventable, asSettable); 

// m now has all methods mixed in. 

,混入真SH应该是TypeScript功能。

+0

我甚至会说第一个版本应该简单地是TypeScript如何实现mixins - wouldn'不要太难。 –

+0

这两个选项的问题,如果我正确理解ts语义,是他们失去了'功能混合'的'功能'部分。你只是用这些属性扩展这个类,让这种混合风格变得更好的事情是,你可以和mixin一起执行代码,这使得你可以节省一些状态或者你需要的任何东西去做。这种功能的使用是国际海事组织(IMO)使JS比任何其他语言都值得(甚至......和整个网络标准的事情......)相比,但除此之外,JS只是一个弱的替代品。 – aaronstacy

+0

我相信你可以使用 var mixed = _.extend(TestMixin.prototype,Mixin); 让生活更轻松 – qbolec

1

一个解决方案是除了使用关键字'new'外,不使用typescript类系统,而只使用类型和接口的系统。

//the function that create class 
function Class(construct : Function, proto : Object, ...mixins : Function[]) : Function { 
     //... 
     return function(){}; 
} 

module Test { 

    //the type of A 
    export interface IA { 
     a(str1 : string) : void; 
    } 

    //the class A 
    //<new() => IA> === cast to an anonyme function constructor that create an object of type IA, 
    // the signature of the constructor is placed here, but refactoring should not work 
    //Class(<IA> { === cast an anonyme object with the signature of IA (for refactoring, but the rename IDE method not work) 
    export var A = <new() => IA> Class(

     //the constructor with the same signature that the cast just above 
     function() { } , 

     <IA> { 
      //!! the IDE does not check that the object implement all members of the interface, but create an error if an membre is not in the interface 
      a : function(str : string){} 
     } 
    ); 


    //the type of B 
    export interface IB { 
     b() : void; 
    } 
    //the implementation of IB 
    export class B implements IB { 
     b() { } 
    } 

    //the type of C 
    export interface IC extends IA, IB{ 
     c() : void; 
     mystring: string; 
    } 

    //the implementation of IC 
    export var C = <new (mystring : string) => IC> Class(

     //public key word not work 
     function(mystring : string) { 

      //problem with 'this', doesn't reference an object of type IC, why?? 
      //but google compiler replace self by this !! 
      var self = (<IC> this); 
      self.mystring = mystring; 
     } , 

     <IC> { 

      c : function(){}, 

      //override a , and call the inherited method 
      a: function (str: string) { 

       (<IA> A.prototype).a.call(null, 5);//problem with call and apply, signature of call and apply are static, but should be dynamic 

       //so, the 'Class' function must create an method for that 
       (<IA> this.$super(A)).a(''); 
      } 

     }, 
     //mixins 
     A, B 
    ); 

} 

var c = new Test.C(''); 
c.a(''); 
c.b(); 
c.c(); 
c.d();//ok error !