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.

Итак, я хочу, чтобы smg выглядел так:
(Этот код не соответствует требованиям)

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?


person nvirth    schedule 04.02.2021    source источник


Ответы (1)


У меня есть решение, которое почти соответствует вашим требованиям. Ограничение состоит в том, что для того, чтобы он работал, getValueForMyProperty должен быть public.

Суть этой настройки заключается в том, что вместо использования общего T для описания возвращаемого типа мы используем T для описания самого объекта. Таким образом, мы можем создавать такие конкретные экземпляры, как это:

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

Для этого мы говорим, что общий T на MyBase должен расширять этот интерфейс:

interface CanGetProperty {
    getValueForMyProperty(): unknown;
}

Интерфейсы Typescript только описывают public свойства объекта и не могут использовать такие модификаторы, как protected или private. Вот почему эта настройка требует, чтобы метод getValueForMyProperty на MyDescendent был общедоступным. В противном случае он не будет выполнять требуемый интерфейс.

У меня как-то не возникало ошибки при реализации protected abstract метода с public методом? Похоже, это должна быть ошибка, поэтому я также удаляю protected из MyBase.

Типы для MyBase теперь немного запутаны, но это нормально, потому что это всего лишь одно место.

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

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

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

Я протестировал его на этой игровой площадке Typescript и получаю хороший вывод о возвращаемом типе для потомков с разными типами .

person Linda Paiste    schedule 04.02.2021
comment
Большое спасибо, это идеально подходит для меня :) - person nvirth; 05.02.2021