2012-10-04 20 views
6

Ich spiele um mit Typoskript, und ich habe ein paar functional mixins bekam, Eventable und Settable, dass ich zu einer Model Klasse mixin möge (so tun, es ist so etwas wie ein Backbone.js Modell):Mixins in Typoskript

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); 

der obige Code funktioniert gut, aber würde nicht kompilieren, wenn ich wie (new Model()).set('foo', 'bar') eine des Mixed-in Verfahren zu verwenden versucht.

Ich kann dieses Problem umgehen, indem

  1. Hinzufügen interface Erklärungen für die Mixins
  2. erklärt Dummy get/set/on/trigger Methoden in der Model Erklärung

Gibt es eine saubere Art und Weise um die Dummy-Deklarationen?

+0

möglicherweise eine Lösung im Zusammenhang zu lösen dieses auf [Microsoft/Typoskript # 2919] (https://github.com/Microsoft/TypeScript/issues/2919#issuecomment-173384825) – mucaho

Antwort

12

Hier ist ein Weg, um Mixins mit interfaces und eine static create() Methode zu nähern. Schnittstellen unterstützen die Mehrfachvererbung, sodass Sie die interfaces für Ihre Mixins nicht neu definieren müssen und die static create()-Methode sorgt dafür, dass Sie eine Instanz von Model() als IModel zurückgeben (die <any> wird benötigt, um eine Compiler-Warnung zu unterdrücken) müssen alle Ihre Mitgliedsdefinitionen für Model auf IModel duplizieren, die saugt, aber es scheint wie der sauberste Weg zu erreichen, was Sie in der aktuellen Version von TypeScript wollen.

bearbeiten: Ich habe einen etwas einfacheren Ansatz zur Unterstützung von Mixins identifiziert und habe sogar eine Hilfsklasse für die Definition von diesen erstellt. Details können over here gefunden werden.

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

war um diese zu veröffentlichen. TypeScript sollte eigentlich erweitert werden, um Mixins von Klassen zu unterstützen, da viele JS-Bibliotheken dies derzeit verwenden (zB Backbone.js). –

+2

+1 für die Notwendigkeit für partielle Klassen und/oder Mixins –

+0

seit ts1.4 können Sie "ISetable & IEvents" statt "IModel" – mif

3

Der sauberste Weg, es zu tun, althought es noch doppelt Typdeklarationen erfordert, ist die mixin als Modul zu definieren:

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"); 

Eine Alternative zur Verwendung von Schnittstellen ist es mit Klassen zu hacken (Obwohl wegen der Mehrfachvererbung, benötigen Sie ein Common-Interface für die Mixins erstellen):

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. 

Als ich auf Steven Antwort kommentierte Mixins wirklich sh Dies wäre eine TypeScript-Funktion.

+0

Ich würde sogar sagen, die erste Version sollte einfach sein, wie TypeScript Mixins implementiert - würde ' t zu hart sein. –

+0

das Problem mit diesen beiden Optionen, wenn ich ts Semantik richtig verstehe, ist, dass sie den "funktionalen" Teil der "funktionalen Mixins" verlieren. Sie erweitern nur die Klasse mit den Eigenschaften, und die Sache, die diesen Stil von Mixins gut macht, ist die Tatsache, dass Sie Code zusammen mit dem Mixin ausführen können, was Ihnen die Möglichkeit gibt, kleine Dinge des Staates zu retten, oder was immer Sie brauchen machen. Diese Art der Nutzung von Funktionen ist IMO, was JS überhaupt etwas wert macht (naja ... und die ganze Web-Standards Sache ...) im Vergleich zu anderen Sprachen, aber ansonsten ist JS nur ein schwacher Ersatz. – aaronstacy

+0

Ich glaube, Sie könnten var mixed = _.extend verwenden (TestMixin.prototype, Mixin); um das Leben zu erleichtern – qbolec

1

Eine Lösung ist, das Typoskript Klassensystem nicht zu verwenden, sondern nur das System der Typen und Schnittstellen, zusätzlich zum Schlüsselwort 'neu'.

//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 !