TypeScript 中的 Mixin

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12719844/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 16:12:06  来源:igfitidea点击:

Mixins in TypeScript

mixinstypescript

提问by aaronstacy

I'm playing around with TypeScript, and I've got a couple functional mixins, Eventableand Settable, that I'd like to mixin to a Modelclass (pretend it's something like a Backbone.js model):

我玩弄打字稿,和我有一对夫妇功能的混入EventableSettable,我想混入到一个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);

The code above works fine, but would not compile if I tried to use one of the mixed-in methods like (new Model()).set('foo', 'bar').

上面的代码工作正常,但如果我尝试使用诸如(new Model()).set('foo', 'bar').

I can work around this by

我可以解决这个问题

  1. adding interfacedeclarations for the mixins
  2. declaring dummy get/set/on/triggermethods in the Modeldeclaration
  1. interface为 mixin添加声明
  2. 声明哑get/ set/ on/trigger在方法Model声明

Is there a clean way around the dummy declarations?

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

采纳答案by Eric Simonton

There is a new way that was build into Typescript a few years ago, called "mixin classes". It's not well covered in the docs, but they do have a well-commented exampleto describe the pattern well. Applied to your situation it could look something like:

几年前,Typescript 内置了一种新方法,称为“mixin 类”。文档中没有很好地涵盖它,但是他们确实有一个评论良好的示例来很好地描述该模式。适用于您的情况,它可能类似于:

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

This gets you the typing you wanted, so that e.g. you are able to call (new Model()).set('boo', 'bar')with full typing support. No dummy declarations.

这让您可以输入您想要的内容,例如,您可以(new Model()).set('boo', 'bar')使用完整的输入支持进行通话。没有虚拟声明。

回答by Steven Ickman

Here's one way to approach mixins using interfacesand a static create()method. Interfaces support multiple inheritance so that prevents you from having to redefine the interfacesfor your mixins and the static create()method takes care of giving you back an instance of Model()as an IModel(the <any>cast is needed to supress a compiler warning.) You'll need to duplicate all of your member definitions for Modelon IModelwhich sucks but it seems like the cleanest way to achieve what you want in the current version of TypeScript.

这是使用interfaces和方法来处理 mixin 的一种static create()方法。接口支持多重继承,这样你就不必interfaces为你的 mixins重新定义 ,并且该static create()方法会照顾你返回一个Model()as an的实例IModel<any>需要强制转换来抑制编译器警告。)你需要复制所有的你ModelIModelwhich 的成员定义很糟糕,但这似乎是在当前版本的 TypeScript 中实现你想要的最干净的方式。

edit: I've identified a slightly simpler approach to supporting mixins and have even created a helper class for defining them. Details can be found over here.

编辑:我已经确定了一种稍微简单的方法来支持 mixin,甚至创建了一个帮助类来定义它们。详细信息可以在这里找到。

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

回答by Michael Sondergaard

The cleanest way to do it, althought it still requires double type declarations, is to define the mixin as a module:

尽管仍然需要双重类型声明,但最简洁的方法是将 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");

An alternative to using interfaces is to hack it with classes (Although because of multiple-inheritance, you'll need to create a common-interface for the mixins):

使用接口的另一种方法是用类来破解它(尽管由于多重继承,您需要为 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 Steven's answer, mixins really should be a TypeScript feature.

当我评论 Steven 的回答时,mixin 真的应该是 TypeScript 的一个特性。

回答by Kimoja_san

One solution is to not use the typescript class system , but just the systeme of types and interfaces, in addition to the keyword 'new'.

一种解决方案是不使用 typescript 类 system ,而只使用类型和接口的 systeme ,以及关键字“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 !