2012-10-04 88 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 !