Проблема с типами объединения и условными типами

У меня есть следующие объявления типов:

class MyGeneric<T> { }

type ReplaceType<T> = T extends Function ? T : MyGeneric<T> | T;

ReplaceType<T> должен преобразоваться в MyGeneric<T> | T или T, в зависимости от того, T это функция или нет:

// Input type:    string
// Expected type: string | MyGeneric<string>
// Actual type:   string | MyGeneric<string>
type Test1 = ReplaceType<string>;

// Input type:    () => void
// Expected type: () => void
// Actual type:   () => void
type Test2 = ReplaceType<() => void>;

К сожалению, это не работает с типами boolean и union:

// Input type:    boolean
// Expected type: boolean | MyGeneric<boolean>
// Actual type:   boolean | MyGeneric<true> | MyGeneric<false>
type Test3 = ReplaceType<boolean>;

// Input type:    "foo" | "bar"
// Expected type: "foo" | "bar" | MyGeneric<"foo" | "bar">
// Actual type:   "foo" | "bar" | MyGeneric<"foo"> | MyGeneric<"bar">
type Test4 = ReplaceType<"foo" | "bar">;

Ссылка на игровую площадку


person dotjpg3141    schedule 16.07.2018    source источник


Ответы (1)


Причина, по которой boolean и объединения имеют аналогичное поведение, заключается в том, что компилятор видит boolean как объединение литеральных типов true и false, поэтому type boolean = true | false (хотя это определение не существует явно)

Причина такого поведения в том, что условный тип по умолчанию распределяется по объединению. Это спроектированное поведение, позволяющее реализовать всевозможные мощные вещи. Вы можете узнать больше по теме здесь

Если вы не хотите, чтобы условные выражения распределялись по объединению, вы можете использовать тип в кортеже (это предотвратит поведение)

class MyGeneric<T> { }

type ReplaceType<T> = [T] extends [Function] ? T : MyGeneric<T> | T;

// Input type:    string
// Expected type: string | MyGeneric<string>
// Actual type:   string | MyGeneric<string>
type Test1 = ReplaceType<string>;

// Input type:    () => void
// Expected type: () => void
// Actual type:   () => void
type Test2 = ReplaceType<() => void>;

// Input type:    boolean
// Expected type: boolean | MyGeneric<boolean>
// Actual type:   boolean | MyGeneric<boolean>
type Test3 = ReplaceType<boolean>;

// Input type:    "foo" | "bar"
// Expected type: "foo" | "bar" | MyGeneric<"foo" | "bar">
// Actual type:   "foo" | "bar" | MyGeneric<"foo" | "bar">
type Test4 = ReplaceType<"foo" | "bar">;
person Titian Cernicova-Dragomir    schedule 16.07.2018
comment
Спасибо, что работает. Я думал, что типы кортежей всегда основаны на типах массивов. - person dotjpg3141; 16.07.2018
comment
Они есть, но расширения будут проверять элементы кортежей на совместимость так же, как и для любого другого члена объекта. - person Titian Cernicova-Dragomir; 16.07.2018