【问题标题】:Mixins in TypeScriptTypeScript 中的 Mixin
【发布时间】:2012-09-25 01:13:32
【问题描述】:

我正在玩 TypeScript,我有几个 functional mixinsEventableSettable,我想将它们混入 Model 类(假设它类似于 Backbone .js 模型):

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. 为 mixins 添加 interface 声明
  2. Model 声明中声明虚拟get/set/on/trigger 方法

有没有绕过虚拟声明的干净方法?

【问题讨论】:

标签: mixins typescript


【解决方案1】:

这是使用interfacesstatic create() 方法来处理mixin 的一种方法。接口支持多重继承,这样您就不必为您的 mixins 重新定义 interfaces,而 static 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');

【讨论】:

  • 正要发布这个。 TypeScript 确实应该被扩展以支持类的混合,因为目前很多 JS 库都使用它(例如 Backbone.js)。
  • 从 ts1.4 开始,您可以使用“ISettable & IEvents”代替“IModel”
【解决方案2】:

虽然它仍然需要双重类型声明,但最简洁的方法是将 mixin 定义为模块:

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

使用接口的另一种方法是使用类来破解它(尽管由于多重继承,您需要为 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.

正如我对 Steven 的回答所评论的,mixin 确实应该是 TypeScript 的功能。

【讨论】:

  • 我什至会说第一个版本应该只是 TypeScript 实现 mixins 的方式——不会太难。
  • 如果我正确理解 ts 语义,这两个选项的问题是它们失去了“功能性混合”的“功能性”部分。你只是用属性来扩展类,使这种风格的 mixin 很好的事情是你可以与 mixin 一起执行代码,这让你有机会保存一点点状态,或者你需要的任何东西去做。与其他语言相比,这种对函数的使用是 IMO 让 JS 变得有价值的原因(嗯......以及整个网络标准......),但除此之外,JS 只是一个弱替代品。
  • 我相信你可以使用 var mixed = _.extend(TestMixin.prototype, Mixin);让生活更轻松
【解决方案3】:

一种解决方案是不使用 typescript 类系统,而只使用类型和接口的系统,以及关键字“new”。

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

【讨论】:

    【解决方案4】:

    几年前在 Typescript 中构建了一种新方法,称为“mixin 类”。文档中没有很好地涵盖它,但他们确实有a well-commented example 来很好地描述这种模式。应用到您的情况,它可能看起来像:

    type Constructor = new (...args: any[]) => {}
    
    function Settable<TBase extends Constructor>(Base: TBase) {
      return class extends Base {
        _props: Record<string, any> = {};
    
        get(key: string) {
          return this._props[key];
        }
    
        set(key: string, value: any) {
          this._props[key] = value;
          return this;
        }
      }
    }
    
    function Eventable<TBase extends Constructor>(Base: TBase) {
      return class extends Base {
        _events: Record<string, () => void> = {};
    
        on(name: string, callback: () => void) {
          this._events[name] = callback;
        }
    
        trigger(name: string) {
          this._events[name].call(this);
        }
      }
    }
    
    class Model extends Settable(Eventable(Object)) {
      constructor(properties = {}) {
        super();
      }
    }
    

    这会让你得到你想要的打字,例如您可以致电(new Model()).set('boo', 'bar') 并提供完整的打字支持。没有虚拟声明。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-22
      • 2018-10-17
      • 1970-01-01
      • 2018-06-30
      • 1970-01-01
      • 2018-07-07
      • 2023-02-20
      相关资源
      最近更新 更多