2012-10-04 12 views
6

estoy jugando un poco con mecanografiado, y tengo un par functional mixins, Eventable y Settable, que me gustaría mixin a una clase Model (pretender que es algo así como un modelo Backbone.js):Mixins a máquina de escribir

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

El código anterior funciona bien, pero no se compilará si trataba de utilizar uno de los métodos mixtos en como (new Model()).set('foo', 'bar').

puedo evitar esto

  1. añadiendo interface declaraciones de los mixins
  2. declaran ficticias get/set/on/trigger métodos en la declaración Model

¿Hay una manera limpia alrededor de las declaraciones ficticias?

+0

posiblemente relacionado una solución para resolver esto en [Microsoft/mecanografiado # 2919] (https://github.com/Microsoft/TypeScript/issues/2919#issuecomment-173384825) – mucaho

Respuesta

12

Aquí hay una forma de acercar mixins usando interfaces y un método static create(). Las interfaces admiten herencia múltiple, por lo que no tendrá que redefinir el interfaces para sus mixins y el método static create() se encarga de devolverle una instancia de Model() como IModel (se necesita el molde <any> para suprimir una advertencia del compilador). necesita duplicar todas las definiciones de miembro para Model en IModel que apesta, pero parece ser la manera más limpia de lograr lo que desea en la versión actual de TypeScript.

editar: He identificado un enfoque un poco más simple para admitir mixins e incluso he creado una clase de ayuda para definirlas. Los detalles se pueden encontrar 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

estaba a punto de publicar esto. TypeScript realmente debería extenderse para admitir mixins de clases, ya que muchas bibliotecas JS usan actualmente eso (por ejemplo, Backbone.js). –

+2

+1 a la necesidad de clases parciales y/o mixins –

+0

desde ts1.4 puede usar "ISettable & IEvents" en lugar de "IModel" – mif

3

La forma más limpia de hacerlo, aunque incluya todavía requiere declaraciones de tipo doble, es definir el mixin como un módulo:

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

una alternativa al uso de interfaces es cortarlo con las clases (Aunque debido a la herencia múltiple, tendrá que crear una interfaz común para los mixins):

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. 

Como les comentaba sobre la respuesta de Steven, mixins realmente sh debería ser una característica de TypeScript.

+0

Incluso diría que la primera versión debería ser simplemente cómo TypeScript implementa mixins - wouldn ' ser demasiado duro –

+0

el problema con estas dos opciones, si entiendo su semántica correctamente, es que pierden la parte 'funcional' de 'mixins funcionales'. solo estás ampliando la clase con las propiedades, y lo que hace que este estilo de mixins sea bueno es el hecho de que puedes ejecutar código junto con la mezcla, lo que te da la oportunidad de guardar pequeños detalles del estado, o lo que sea que necesites. que hacer. este tipo de uso de funciones es IMO lo que hace que JS valga algo en absoluto (bueno ... eso y todo lo relacionado con los estándares web ...) en comparación con otros lenguajes, pero de lo contrario JS es solo un reemplazo débil. – aaronstacy

+0

Creo que podría usar var mixed = _.extend (TestMixin.prototype, Mixin); para hacer la vida más fácil – qbolec

1

Una solución es no utilizar el sistema de clase de mecanografía, sino solo el sistema de tipos e interfaces, además de la palabra clave 'nuevo'.

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