Несколько обобщенных c ограничений с настройками по умолчанию, зависящими друг от друга - PullRequest
0 голосов
/ 03 марта 2020

Это моя структура интерфейса / класса Typescript, которая у меня есть:

interface IBaseOptions {
    homeUrl: string;
}

abstract class BaseApp<TOptions extends IBaseOptions = IBaseOptions> {
    constructor(
        private options: TOptions
    ) {
        // does nothing
    }
}

// -----------------------------

interface ICalls {
    getValue: () => number;
}

interface IOptions<TCalls extends ICalls = ICalls> extends IBaseOptions {
    calls: TCalls;
}

class App<TOptions extends IOptions<TCalls> = IOptions<TCalls>, TCalls extends ICalls = ICalls> extends BaseApp<TOptions> {
                                   -------- (ts2744 error here)
    constructor(options: TOptions) {
        super(options);
    }
}

class SubApp extends App {
    // whatever implementation
}

Я хотел бы указать значения по умолчанию, чтобы у меня не было конкретных типов для моих опций и вызовов. То, как я определил мои типы, приводит к ошибке компиляции (ошибка ts2744).

Я также хотел бы избежать обмена типами generi c (с ограничениями и значениями по умолчанию), чтобы я оставил первый generi c type для опций и второй вызов.

Можно ли сначала определить обобщенные типы c с ограничениями, а затем установить их значения по умолчанию?

Вы можете отметьте это Playground Link

1 Ответ

0 голосов
/ 09 марта 2020

Очевидное исправление, поменяйте местами порядок параметров типа, у вас не работает. Поэтому мы должны прибегнуть к менее очевидным исправлениям. Общая идея здесь такова: если вы не можете установить значение по умолчанию на то, что вам нужно, установите его на фиктивное значение, а затем, при использовании типа, проверьте значение фиктивного значения и используйте вместо него исходное желаемое значение по умолчанию. Так что Foo<T=Default<U>, U=X> ... T становится чем-то вроде Foo<V=DefaultSigil, U=X> ... V extends DefaultSigil ? Default<U> : V.

Вот один из способов сделать это:

type OrDefault<T> = [T] extends [never] ? IOptions<ICalls> : T;

class App<O extends IOptions<C> = never, C extends ICalls = ICalls>
    extends BaseApp<OrDefault<O>> {
    constructor(options: OrDefault<O>) {
        super(options);
    }
}

В этом случае мы используем never в качестве фиктивного значения по умолчанию; если кто-то вручную не определит never для O, это безопасное значение для использования в качестве фиктивного. Затем OrDefault проверяет, является ли его параметр never или нет, и возвращает IOptions<ICalls>, если это так.

Да, вы заметили, что проверка OrDefault вместо [T] extends [never] ? ... : ... T extends never ? ... : .... Причина, по которой я это сделал, заключается в том, чтобы избежать распределения условного типа по T. Так как T является «параметром обнаженного типа», когда вы его проверяете, как T extends never ? ... : ..., компилятор попытается интерпретировать T как объединение, разделить объединение на члены, выполнить условное выражение, а затем объединить результаты обратно в союз. Если вы передадите never для T, это будет выглядеть как «пустой союз», и результат всегда будет never. Мы не хотим, чтобы OrDefault<never> было never, поэтому нам не нужны дистрибутивные условные типы. Самый простой обходной путь - не допустить, чтобы проверка была «голым» параметром типа, «одев» его в один кортеж. [T] не разбивается на составляющие объединения, и поэтому OrDefault<never> будет IOptions<ICalls> по желанию.

И вместо использования O позже мы используем OrDefault<O>. Вы должны быть в состоянии убедиться, что это работает так, как вы хотите. Это неуклюже и запутанно, но это работает.


Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...