类型注释:如何从多个类的Mixin继承并保留静态方法?

时间:2018-11-07 14:42:39

标签: typescript typescript-typings

  

这只是ts注释问题,在js运行时中,一切都能按预期进行。

对于多重继承/混合,我们有一个运行时方法,该方法采用类/对象并创建compound(mixed)类。

class A {
    a: string
    static staticA: string
}
class B {
    b: string
    static staticB: string
}
class C extends mixin(A, B) {
    c: string
    static staticC: string
}

因此我们的mixin方法创建了C继承的混合类。现在我们有一些注释问题。简单的mixin声明看起来像这样(实际上,mixin也接受T1T2的对象,但为简单起见,我将其从代码中删除了):

interface Constructor<T = {}> {
    new (...args: any[]): T;    
}

declare function mixin<T1 extends Constructor, T2 extends Constructor> (
    mix1: T1, 
    mix2: T2
): new (...args) => (InstanceType<T1> & InstanceType<T2>)

不幸的是,mixin返回的类型失去了T1T2的静态方法。

C. /* only 'staticC' is present in autocomplete */
let c = new C;
c. /* all - 'a', 'b' and 'c' are present in autocomplete */

我也尝试返回类型T1 & T2,但在mixin(A, B)中得到了错误

  

[ts]基本构造函数必须都具有相同的返回类型。

对此有什么解决办法吗?


最终解决方案

感谢@ titian-cernicova-dragomir。我在这里添加了我的最终解决方案,我扩展了注解以支持对象,不仅是类,还希望对某些人有所帮助。

// Extract static methods from a function (constructor)
type Statics<T> = {
    [P in keyof T]: T[P];
}

declare function mixin<
    T1 extends Constructor | object, 
    T2 extends Constructor | object,
    T3 extends Constructor | object = {},
    T4 extends Constructor | object = {},
    > (
        mix1: T1,
        mix2: T2,
        mix3?: T3,
        mix4?: T4,
    ): 
    (T1 extends Constructor ? Statics<T1> : {}) & 
    (T2 extends Constructor ? Statics<T2> : {}) &
    (T3 extends Constructor ? Statics<T3> : {}) &
    (T4 extends Constructor ? Statics<T4> : {}) &
    (new (...args: T1 extends Constructor ? ConstructorParameters<T1> : never[]) =>
        (T1 extends Constructor ? InstanceType<T1> : T1) &
        (T2 extends Constructor ? InstanceType<T2> : T2) &
        (T3 extends Constructor ? InstanceType<T3> : T3) &
        (T4 extends Constructor ? InstanceType<T4> : T4)
    );



class A {
    a: string
    static staticA: string
}
class B {
    b: string
    static staticB: string
}
const Utils = {
    log () {}
}
class C extends mixin(A, B, Utils) {
    c: string
    static staticC: string
}
C. // has 'staticA', 'staticB', 'staticC'
let c = new C;
c. // has 'a', 'b', 'c', 'log'

我还从第一类的构造函数(如果有)中添加了参数支持。

...args: T1 extends Constructor ? ConstructorParameters<T1> : never[]

不幸的是,我找不到使mixin批注支持任何数量的参数的解决方案,目前我提出了4种,因为它足以满足我的情况。尽管我们的js mixin可以接受任意数量的类/对象来创建混合类。

2 个答案:

答案 0 :(得分:1)

我看不到您的混合机制...但是它看起来像原始版本,而不是更新版本。

下面摘自TypeScript Mixins Part Three的示例表明,使用这种创建混合方法时,静态属性是根据类型和运行时行为来处理的。

具有静态成员的小游戏

type Constructor<T = {}> = new (...args: any[]) => T;

function Flies<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        static altitude = 100;
        fly() {
            console.log('Is it a bird? Is it a plane?');
        }
    };
}

function Climbs<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        static stickyHands = true;
        climb() {
            console.log('My spider-sense is tingling.');
        }
    };
}

class Hero {
    constructor(private name: string) {

    }
}

const HorseFlyWoman = Climbs(Flies(Hero));

const superhero = new HorseFlyWoman('Shelley');
superhero.climb();
superhero.fly();

console.log(HorseFlyWoman.stickyHands);
console.log(HorseFlyWoman.altitude);

可运行版本

这里是转译版本,因此您可以看到输出:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
function Flies(Base) {
    var _a;
    return _a = /** @class */ (function (_super) {
            __extends(class_1, _super);
            function class_1() {
                return _super !== null && _super.apply(this, arguments) || this;
            }
            class_1.prototype.fly = function () {
                console.log('Is it a bird? Is it a plane?');
            };
            return class_1;
        }(Base)),
        _a.altitude = 100,
        _a;
}
function Climbs(Base) {
    var _a;
    return _a = /** @class */ (function (_super) {
            __extends(class_2, _super);
            function class_2() {
                return _super !== null && _super.apply(this, arguments) || this;
            }
            class_2.prototype.climb = function () {
                console.log('My spider-sense is tingling.');
            };
            return class_2;
        }(Base)),
        _a.stickyHands = true,
        _a;
}
var Hero = /** @class */ (function () {
    function Hero(name) {
        this.name = name;
    }
    return Hero;
}());
var HorseFlyWoman = Climbs(Flies(Hero));
var superhero = new HorseFlyWoman('Shelley');
superhero.climb();
superhero.fly();
console.log(HorseFlyWoman.stickyHands);
console.log(HorseFlyWoman.altitude);

答案 1 :(得分:1)

您可以按原样保留原始的mixin函数,而只用T1T2与返回的构造函数相交

class A {
    a!: string
    static staticA: string
}
class B {
    b!: string
    static staticB: string
}
class C extends mixin(A, B) {
    c!: string
    static staticC: string
}

interface Constructor<T = {}> {
    new(...args: any[]): T;
}

declare function mixin<T1 extends Constructor, T2 extends Constructor>(
    mix1: T1,
    mix2: T2
): {
    new(...args: any[]): (InstanceType<T1> & InstanceType<T2>)
} & T1 & T2

C.staticA
C.staticB
C.staticC
let c = new C;
c.a
c.b
c.c

Playground link

您提到您尝试过T1 & T2的方法的问题是不会更改构造函数以返回(InstanceType<T1> & InstanceType<T2>)。您必须将此新签名添加到构造函数和原始类中。