打字稿交换性混合素组成

时间:2018-07-09 17:19:02

标签: typescript

说我有一个带有方法的基本编码器类

encode(obj: {body: Uint8Array}): Uint8Array

并且我将其扩展为通过一个/多个mixins接受不同的obj.body类型-即:

function numberMixin(encoder) {
    return class extends encoder {
       encode(obj) {
           if (isNumberObj(obj)) {
              ...
              return super.encode(transformed);
           }
           return super.encode(obj);
        }
    }
}

并编写这些mixins

const Encoder = etcMixin(numberMixin(stringMixin(Base)));

有没有一种方法可以注释每个mixin /编码器,以便生成的类的encoding方法知道它接受哪种类型?我可以指定输入/输出功能,但是mixin的顺序很严格,不能轻易扩展。

一个选择是在每个mixin上使用泛型类型参数,但是随后我必须在合成的每个步骤中指定类型,这似乎是多余的/太冗长了。

numberMixin<U, T extends Constructor<IEncoder<U>>>(encoder: T): T & Constructor<IEncoder<Number|U>>;

1 个答案:

答案 0 :(得分:1)

我们可以利用以下事实:将函数类型的交集与重载函数视为相同,因此我们可以将编码类型构造为BaseClass['encode'] & newOverlaodSignature

唯一的麻烦是我们不能使用方法语法来做到这一点,我们需要使用函数字段语法,这意味着我们将声明字段,但是将其手动分配给原型。另外,由于我们是手动添加函数,因此我们无法使用super.语法,因此我们将需要手动调用基类实现,因此我们在那里失去了一些类型安全性。

好消息是呼叫站点看起来不错,并且所有重载都存在:

修改 评论中的额外功劳,还会收集类型,以便我们在其他mixin中使用它们。如果我们向类型添加一个额外的静态属性,该属性将包含每个添加的类型的字段,则可以执行此操作。我们需要一个字段而不是该类型的字段的原因是,返回的类型将是AddedStuff & T,所以这将滴流到类型字段,因为我们从中得到所有定义的type字段的交集所有混合。 printEncodedMixin使用额外的类型信息:

class EncoderBase {
    constructor(param: string) {

    }
    encode(obj: { body: Uint8Array }): Uint8Array {
        return obj.body;
    }
    static type: { Uint8Array: Uint8Array }
}
type EncoderType = {
    new (...args: any[]) : { encode: (obj: { body: Uint8Array }) => Uint8Array }
    type: any
}

function numberMixin<T extends EncoderType>(encoder: T) {
    function isNumberObj(obj: any): obj is { body: number } {
        return obj && typeof obj.body === 'number';
    }
    let resultClass = class extends encoder {
        encode!: InstanceType<T>['encode'] & ((obj: { body: number }) => Uint8Array);
        static type : { number : number }
    }
    resultClass.prototype.encode = function (obj: any) {
        if (isNumberObj(obj)) {
            return encoder.prototype.encode(obj);
        }
        return encoder.prototype.encode(obj);
    }

    return resultClass;
}

function stringMixin<T extends EncoderType>(encoder: T) {
    function isStringObj(obj: any): obj is { body: string } {
        return obj && typeof obj.body === 'string';
    }
    let resultClass = class extends encoder {
        encode!: InstanceType<T>['encode'] & ((obj: { body: string }) => Uint8Array);
        static type : { string : string }
    }
    resultClass.prototype.encode = function (obj: any) {
        if (isStringObj(obj)) {
            return encoder.prototype.encode(obj);
        }
        return encoder.prototype.encode(obj);
    }
    return resultClass;
}

function printEncodedMixin<T extends EncoderType>(encoder: T) {
    type Body<T> = T extends any ? {body : T} : never;
    return class extends encoder {
        printEncoded(b: Body<T['type'][keyof T['type']]>) {

        }
    }
}

const Encoder = printEncodedMixin(numberMixin(stringMixin(EncoderBase)));

let d = new Encoder(""); // ctor params still work
d.encode({ body: "" });
d.encode({ body: 0 });
d.encode({ body: {} }); // Error
d.printEncoded({ body: "" }) //  printEncoded({body: string;} | {body: number;} | {body: Uint8Array;}): void