Mixers in TypeScript

I play with TypeScript, and I have a couple of functional mixins , Eventable and Settable that I would like to mix with the Model class (pretend something like the Backbone.js model):

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

The code above works fine, but does not compile if I try to use one of the mixed methods, for example (new Model()).set('foo', 'bar') .

I can get around this with

  • adding interface declarations for mixins
  • Declare dummy get / set / on / trigger methods in the Model declaration

Is there a clean way around dummy data declarations?

+6
source share
3 answers

Here is one way to approach mixins using the interfaces and static create() methods. Interfaces support multiple inheritance, so you do not need to override interfaces for your mixins, and the static create() method allows you to return an instance of Model() as an IModel ( <any> is required). to suppress the compiler warning.) You will need to duplicate all your member definitions for Model on IModel , which sucks, but it seems like this is the cleanest way to achieve what you want in the current version of TypeScript.

edit: I defined a slightly simpler approach to mixins support and even created a helper class to define them. Details can be found.

 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'); 
+12
source

The cleanest way to do this, although it still requires a double type declaration, is to define mixin as a module:

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

An alternative to using interfaces is to hack it using classes (although due to multiple inheritance you need to create a common interface for 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. 

As I commented on Stephen's answer, mixins really should be TypeScript.

+3
source

One solution is not to use the typescript class system, but only the type system and interfaces, in addition to the "new" keyword.

  //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(''); ca(''); cb(); cc(); cd();//ok error ! 
+1
source

Source: https://habr.com/ru/post/926905/


All Articles