Преобразование типа объединения в тип пересечения

Есть ли способ преобразовать тип объединения в тип пересечения:

type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void

Я хотел бы применить преобразование к FunctionUnion чтобы получить FunctionIntersection

Ответ 1

Вы хотите, чтобы союз до пересечения? Распределительные условные типы и вывод из условных типов могут сделать это. (Не думаю, что возможно сделать пересечение в союз, хотя, извините) Здесь злая магия:

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

Это распределяет союз U и переупаковывает его в новый союз, где все составляющие находятся в противоположном положении. Это позволяет выводить тип как пересечение I, как упомянуто в справочнике:

Аналогично, несколько кандидатов на одну и ту же переменную типа в противоположных позициях приводят к выводу типа пересечения.


Давайте посмотрим, работает ли это.

Сначала позвольте мне заключить в скобки ваши FunctionUnion и FunctionIntersection потому что TypeScript, кажется, связывает объединение/пересечение более тесно, чем возврат функции:

type FunctionUnion = (() => void) | ((p: string) => void);
type FunctionIntersection = (() => void) & ((p: string) => void);

Тестирование:

type SynthesizedFunctionIntersection = UnionToIntersection<FunctionUnion>
// inspects as 
// type SynthesizedFunctionIntersection = (() => void) & ((p: string) => void)

Выглядит хорошо!

Будьте осторожны, что в общем случае UnionToIntersection<> раскрывает некоторые детали того, что TypeScript считает действительным объединением. Например, boolean внутренне представляется как true | false true | false, так

type Weird = UnionToIntersection<string | number | boolean>

становится

type Weird = string & number & true & false

Надеюсь, это поможет. Удачи!

Ответ 2

Если вы пытаетесь преобразовать массив в пересечение, отображение массива заранее приведет к тому, что полученное объединение массивов будет иметь неправильный порядок.

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

Ниже поддерживаются массивы до индекса 9, который можно расширить вручную, если вам нужна более длинная рекурсия.

type Idx<T extends any, K extends keyof any, Yes> = T extends Record<K, any>
  ? T[K] & Yes
  : unknown

type ArrayToIntersection<T extends any[]> = Idx<T, '0', Idx<T, '1', Idx<T, '2', Idx<T, '3', Idx<T, '4', Idx<T, '5', Idx<T, '6', Idx<T, '7', Idx<T, '8', Idx<T, '9', unknown>>>>>>>>>>

// Usage: => 1 & 2 & 3 & 4
type Result = ArrayToIntersection<[1, 2, 3, 4]>