TypeScript中的类型安全的mixin装饰器

Kii*_*age 8 generics decorator mixins typescript

我试着定义类型安全的mixin()装饰函数,如下所示,

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

function mixin<T>(MixIn: Constructor<T>) {
    return function decorator<U>(Base: Constructor<U>) : Constructor<T & U> {
        Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
            Base.prototype[name] = MixIn.prototype[name];
        });

        return Base as Constructor<T & U>;
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用如下,

class MixInClass {
    mixinMethod() {console.log('mixin method is called')}
}

/**
 *  apply mixin(MixInClass) implicitly (use decorator syntax)
 */
@mixin(MixInClass)
class Base1 {
    baseMethod1() { }
}
const m1 = new Base1();
m1.baseMethod1();
m1.mixinMethod(); // error TS2339: Property 'mixinMethod' does not exist on type 'Base1'.
Run Code Online (Sandbox Code Playgroud)

然后,编译器说m1没有成员'mixinMethod'.

生成的代码如下,

//...
var Base1 = /** @class */ (function () {
    function Base1() {
    }
    Base1.prototype.baseMethod1 = function () { };
    Base1 = __decorate([
        mixin(MixInClass)
    ], Base1);
    return Base1;
}());
//...
Run Code Online (Sandbox Code Playgroud)

它看起来mixin装饰器已正确应用.

所以,根据我的理解,m1推断的类型是Base1 & MixIn.但编译器说它只是Base1.

我用旗帜tsc 2.6.2编译了这些代码--experimentalDecorators.

为什么编译器无法像我预期的那样识别类型?


基于@jcalz的回答,我修改了我的代码如下,

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

function mixin<T1, T2>(MixIns:  [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin(MixIns) {
    class Class{ };

    for (const MixIn of MixIns) {
        Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
            Class.prototype[name] = MixIn.prototype[name];
        });
    }

    return Class;
}

class MixInClass1 {
    mixinMethod1() {}
}

class MixInClass2 {
    mixinMethod2() {}
}

class Base extends mixin([MixInClass1, MixInClass2]) {
    baseMethod() { }
}

const x = new Base();

x.baseMethod(); // OK
x.mixinMethod1(); // OK
x.mixinMethod2(); // OK
x.mixinMethod3(); // Property 'mixinMethod3' does not exist on type 'Base' (Expected behavior, Type check works correctly)
Run Code Online (Sandbox Code Playgroud)

这非常有效.我想mixin为可变长度的mixin类改进这个函数.

一种解决方案是添加如下的重载函数声明,

function mixin<T1>(MixIns: [Constructor<T1>]): Constructor<T1>;
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin<T1, T2, T3>(MixIns: [Constructor<T1>, Constructor<T2>, Constructor<T3>]): Constructor<T1&T2&T3>;
Run Code Online (Sandbox Code Playgroud)

但这太难看了.有什么好主意吗?在支持变量类型之前是不可能的?

jca*_*alz 8

装饰器不会以您期望的方式改变装饰类的类型签名.在Github中有一个相当冗长的问题讨论了这个问题,并且不清楚是否应该如何(或者是否)应该实现这种突变.现在的主要问题是编译器理解Base1为未修饰的类,并且没有装饰版本的名称.

从阅读Github问题看起来,看起来建议的解决方法(至少现在)是这样的:

class Base1 extends mixin(MixInClass)(
  class {
    baseMethod1() { }
  }) {
}
Run Code Online (Sandbox Code Playgroud)

所以,你不使用的装饰@符号,和而不直接应用的装饰功能,一个匿名类(具有相同的实现你想要的Base1),然后继承得到Base1.现在编译器理解它Base1既有a baseMethod1()又有a mixinMethod().

希望你觉得有帮助.祝好运!