TypeScript: Изведете типа на реализацията на абстрактния метод

Бих искал да имам базов клас със свойство, чийто тип се извежда от върнатата стойност на метода, който метод се изпълнява от низходящи класове.

И така, базовият клас може да бъде smg така:

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 Playground и получавам добро заключение за връщания тип на наследници с различни типове .

person Linda Paiste    schedule 04.02.2021
comment
Благодаря много, това е идеално за мен :) - person nvirth; 05.02.2021