网站搜索

TypeScript:推断抽象方法实现的类型


我希望有一个基类,它有一个属性,其类型是从一个方法的返回值推断出来的,而该方法由子类实现。

所以,基类可以像这样:

abstract class MyBase<T> {
    protected myProperty: T;

    constructor() {
        this.myProperty = this.getValueForMyProperty();
    }

    protected abstract getValueForMyProperty(): T;
}

以及一个后代:

class MyDescendant extends MyBase<MyDescendant1PropertyType> { // MyDescendant1PropertyType should be like ReturnType<(MyDescendant.getValueForMyProperty)>
    protected getValueForMyProperty(): MyDescendant1PropertyType {
        return {
            prop1: "smg"
        };
    }
}
type MyDescendant1PropertyType = {
    prop1
}

现在这可以工作,但在类型定义上有很多样板噪音。
我想描述这个规则:

MyBase.myProperty 的类型是 MyDescendant.getValueForMyProperty 类型。

我不需要 MyBase 中的类型信息,只需要 MyDescendant 中的。

所以我想要的是像这样的东西:
(这段代码无法编译)

abstract class MyBase<T /*infer from below, don't want to type it in descendants*/> {
    protected myProperty: ReturnType<getValueForMyProperty>; // Don't know the right syntax for this

    constructor() {
        this.myProperty = this.getValueForMyProperty();
    }

    protected abstract getValueForMyProperty(): T;
}

class MyDescendant extends MyBase {     // Don't want to add the generic part, MyBase<anything>
    protected getValueForMyProperty() { // Don't want to manually add return type
        return {                        // Return type should be inferred
            prop1: "smg"
        };
    }
}

我可以在 TypeScript 中做这个吗?

答案#1

我有一个几乎符合你需求的解决方案。限制是为了让它工作,getValueForMyProperty 必须是 public。

这个设置的关键在于,我们不是用通用的 T 来描述返回类型,而是用 T 来描述对象本身。这样,我们就可以像这样创建特定实例:

class MyDescendant extends MyBase<MyDescendant> {
    public getValueForMyProperty() {
        return {
            prop1: "smg"
        };
    }
}

为了做到这一点,我们说 MyBase 上的泛型 T 必须扩展这个接口:

interface CanGetProperty {
    getValueForMyProperty(): unknown;
}

TypeScript 接口只描述对象的 public 属性,不能使用像 protected 或 private 这样的修饰符。这就是为什么这个设置要求 MyDescendent 上的 getValueForMyProperty 方法必须是 public 的。否则,它将无法满足所需的接口。

不知为何,当我用一个 public 方法实现 protected abstract 方法时,并没有出现错误?这似乎应该是一个错误,所以我也从 MyBase 中移除了 protected。

MyBase 的类型现在有点乱,但没关系,因为这里只是一个地方。

abstract class MyBase<T extends CanGetProperty> {
    protected myProperty: ReturnType<T['getValueForMyProperty']>;

    constructor() {
        this.myProperty = this.getValueForMyProperty();
    }

    abstract getValueForMyProperty(): ReturnType<T['getValueForMyProperty']>;
}

我在这个 TypeScript Playground 中进行了测试,并且我在具有不同类型的子元素上获得了很好的返回类型推断。