Коли умовний тип перевіряє «голий» параметр типу (T extends ..., а не T[] чи [T]), а в T передано об'єднання, умова застосовується до кожного члена окремо, і результати об'єднуються:
type ToArray<T> = T extends unknown ? T[] : never;
type A = ToArray<string | number>;
// = ToArray<string> | ToArray<number>
// = string[] | number[]
Навіщо так зроблено: на цьому побудовані фільтри об'єднань.
type Exclude<T, U> = T extends U ? never : T;
type R = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
Кожен член перевіряється окремо, never для відкинутих - зникає з об'єднання.
Коли розподіл заважає - потрібно перевірити об'єднання цілком:
type ToArrayAll<T> = [T] extends [unknown] ? T[] : never;
type B = ToArrayAll<string | number>; // (string | number)[]
Загортання в кортеж ([T]) робить параметр «не голим» - розподілу немає.
Найвідоміша пастка - never:
type IsNever<T> = T extends never ? true : false;
type X = IsNever<never>; // never, а не true!
never - порожнє об'єднання. Розподіл по порожньому об'єднанню дає порожній результат, тобто never. Правильна перевірка:
type IsNever<T> = [T] extends [never] ? true : false;
type Y = IsNever<never>; // true
Ще приклади, де важливо розуміти розподіл:
boolean- цеtrue | false, тожT extends true ? 'yes' : 'no'дляbooleanдасть'yes' | 'no';- перевірка, чи тип є об'єднанням - на основі порівняння розподіленого й нерозподіленого результатів;
keyofоб'єднання - лише спільні ключі, а розподільнийT extends unknown ? keyof T : never- усі ключі всіх членів.
Правило: розподіл є лише тоді, коли параметр типу стоїть зліва від extends сам по собі і в нього підставлено об'єднання. T[] extends ..., [T] extends ..., Promise<T> extends ... - не розподіляються.