如果继承 mixin 抽象类的类没有实现 getter,TypeScript 编译器不会抱怨

vid*_*ern 8 typescript

编辑:根据下面@artur-grzesiak 的评论,我们将 Playground 修改为更简单的版本,没有命名错误的interface方法。我们仍然希望编译器针对 not Implemented 抛出错误getInterface,但它不会这样做:

新游乐场


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

// Raw objects interfaces
interface IBaseDataObject {
    readonly id: string;
}
// Name Pattern
interface Name {
    name: string;
}

// interfaces that classes must implement
interface BaseDataObjectInterface<T extends IBaseDataObject> {
    readonly id: string;
    readonly interface: T;
}

abstract class AbstractBaseObject<T extends IBaseDataObject> {
    readonly id: string;
    abstract readonly interface: T

    constructor(
        iBaseDataObject: T
    ) {
        this.id = iBaseDataObject.id;
    }
}

type AbstractBaseObjectCtor<T extends IBaseDataObject> = GConstructor<AbstractBaseObject<T>>;

// Country interface, class and instances
interface ICountry extends IBaseDataObject, Name {}

function NameAbstractMixin<TBase extends AbstractBaseObjectCtor<T>, T extends IBaseDataObject & Name>(Base: TBase) {
    abstract class NamedBase extends Base implements Name {
        readonly name: string;

        constructor(...args: any[]) {
            super(...args)
            this.name = args[0].name;
        }

    }
    return NamedBase;
}


class Country extends NameAbstractMixin(AbstractBaseObject<ICountry>) implements BaseDataObjectInterface<ICountry> {
    // get interface(): ICountry {
    //     return {
    //         id : "hello",
    //         name: "France",
    //     }
    // }
}
Run Code Online (Sandbox Code Playgroud)

AbstractBaseObjectCountry 类应该强制执行从声明抽象属性的类继承的抽象契约interface,并且该类Country应该实现BaseDataObjectInterface需要相同属性/访问器的类,但打字稿编译器不会引发任何错误,正如您在此打字稿游乐场上看到的那样。

操场

我在这里错过了什么吗?

Dim*_*ava 1

如果删除与错误本身无关的所有内容,问题将简化为:

type GConstructor<T = {}> = abstract new (...args: any[]) => T;
type ICountry = 'ICountry' | 'IAnotherCountry'

abstract class AGetInterface<T> {
    abstract readonly getValue: T
}

function NameAbstractMixin1<TBase extends GConstructor<AGetInterface<T>>, T>(Base: TBase) {
    abstract class NamedBase extends Base { }
    return NamedBase;
}

class Country1 extends NameAbstractMixin1(AGetInterface<ICountry>) {
    // get getValue(): ICountry {
    //     return 'ICountry';
    // }
}

Run Code Online (Sandbox Code Playgroud)

让我们尝试详细添加编译器应该猜测的类型:

// Non-abstract class 'Country' does not implement inherited abstract member
class Country2 extends NameAbstractMixin1<GConstructor<AGetInterface<ICountry>>, ICountry>(AGetInterface<ICountry>) {
    // get getValue(): ICountry {
    //     return 'ICountry';
    // }
}
Run Code Online (Sandbox Code Playgroud)

所以这就是原因,编译器无法猜测类型

让我们尝试帮助编译器正确猜测类型

type GuessType3<C> = C extends GConstructor<AGetInterface<infer T>> ? T : never;

function NameAbstractMixin3<TBase extends GConstructor<AGetInterface<T>>, T = GuessType3<TBase>>(Base: TBase) {
    abstract class NamedBase extends Base { }
    return NamedBase;
}
// Non-abstract class 'Country3' does not implement inherited abstract member 'getValue'
class Country3 extends NameAbstractMixin3(AGetInterface<ICountry>) { }
Run Code Online (Sandbox Code Playgroud)

现在检查它在初始代码中是否有效

type GuessType<TBase> = TBase extends AbstractBaseObjectCtor<infer T> ? T : never
function NameAbstractMixin<TBase extends AbstractBaseObjectCtor<T>, T extends IBaseDataObject & Name = GuessType<TBase>>(Base: TBase) { ... }
// Non-abstract class 'Country' does not implement inherited abstract member 'getInterface'
class Country extends NameAbstractMixin(AbstractBaseObject<ICountry>) { ... }
Run Code Online (Sandbox Code Playgroud)

确实如此


通常在这种情况下尝试提供一些开箱即用的类型参数,

function NameAbstractMixin4<T>() {
    return function <TBase extends GConstructor<AGetInterface<T>>>(Base: TBase) {
        abstract class NamedBase extends Base { }
        return NamedBase;
    }
}
// Non-abstract class 'Country4' does not implement inherited abstract member 'getValue' 
class Country4 extends NameAbstractMixin4<ICountry>()(AGetInterface<ICountry>) { }
Run Code Online (Sandbox Code Playgroud)

完美地工作,例如


顺便说一句,直到现在我才知道您可以在使用 T 类型参数之后放置它

如果您知道如何在没有双功能的情况下拆分类型参数,请发表评论,我一直在寻找