将联合类型转换为交集类型

时间:2018-05-16 15:33:58

标签: typescript

有没有办法将联合类型转换为交集类型:

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

我想将转化应用于FunctionUnion以获取FunctionIntersection

4 个答案:

答案 0 :(得分:71)

你想要工会交汇吗? Distributive conditional typesinference from conditional types可以做到这一点。 (不要认为它可以做到交集到对方,对不起)这是邪恶的魔法:

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

分配联盟U并将其重新包装到一个新的联盟中,所有的成员都处于逆变状态。这允许将类型推断为交集I,如手册中所述:

  

同样,反变量位置中相同类型变量的多个候选者会导致交叉类型被推断。

让我们看看它是否有效。

首先让我为您的FunctionUnionFunctionIntersection加上括号,因为TypeScript似乎比函数返回更紧密地绑定了union / intersection:

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,因此

type Weird = UnionToIntersection<string | number | boolean>

变为

type Weird = string & number & true & false

希望有所帮助。祝你好运!

答案 1 :(得分:0)

如果您尝试将数组转换为交集,则事先映射该数组将导致resulting array union to have the wrong order

如果得出的相交顺序无关紧要,则可能无关紧要,但如果上述答案无效,则不会起作用。

以下支持的索引最多为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]>

答案 2 :(得分:0)

当您想要几种类型的交集,但不一定要将并集转换为交集时,还有一个非常相关的问题。没有求助于临时工会,就没有办法直达十字路口!

问题在于我们想要获取交集的类型可能在内部具有并集,这些并集也将转换为交集。救援人员:

// union to intersection converter by @jcalz
// Intersect<{ a: 1 } | { b: 2 }> = { a: 1 } & { b: 2 }
type Intersect<T> = (T extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never

// get keys of tuple
// TupleKeys<[string, string, string]> = 0 | 1 | 2
type TupleKeys<T extends any[]> = Exclude<keyof T, keyof []>

// apply { foo: ... } to every type in tuple
// Foo<[1, 2]> = { 0: { foo: 1 }, 1: { foo: 2 } }
type Foo<T extends any[]> = {
    [K in TupleKeys<T>]: {foo: T[K]}
}

// get union of field types of an object (another answer by @jcalz again, I guess)
// Values<{ a: string, b: number }> = string | number
type Values<T> = T[keyof T]

// TS won't believe the result will always have a field "foo"
// so we have to check for it with a conditional first
type Unfoo<T> = T extends { foo: any } ? T["foo"] : never

// combine three helpers to get an intersection of all the item types
type IntersectItems<T extends any[]> = Unfoo<Intersect<Values<Foo<T>>>>

type Test = [
    { a: 1 } | { b: 2 },
    { c: 3 },
]

// this is what we wanted
type X = IntersectItems<Test> // { a: 1, c: 3 } | { b: 2, c: 3 }

// this is not what we wanted
type Y = Intersect<Test[number]> // { a: 1, b: 2, c: 3 }

给定示例中的执行如下

IntersectItems<[{ a: 1 } | { b: 2 }, { c: 3 }]> =
Unfoo<Intersect<Values<Foo<[{ a: 1 } | { b: 2 }, { c: 3 }]>>>> =
Unfoo<Intersect<Values<{0: { foo: { a: 1 } | { b: 2 } }, 1: { foo: { c: 3 } }}>>> =
Unfoo<Intersect<{ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }>> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }) extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } extends any ? ((x: T) => 0) : never) | ({ foo: { c: 3 } } extends any ? ((x: T) => 0) : never)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(((x: { foo: { a: 1 } | { b: 2 } }) => 0) | ((x: { foo: { c: 3 } }) => 0)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<{ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } }> =
({ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } })["foo"] =
({ a: 1 } | { b: 2 }) & { c: 3 } =
{ a: 1 } & { c: 3 } | { b: 2 } & { c: 3 }

希望这也显示了其他一些有用的技术。

答案 3 :(得分:0)

我稍微扩展了@jcalz 的回答,以解决他描述的布尔问题。

type UnionToIntersectionHelper<U> = (
  U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
  ? I
  : never;

type UnionToIntersection<U> = boolean extends U
  ? UnionToIntersectionHelper<Exclude<U, boolean>> & boolean
  : UnionToIntersectionHelper<U>;

这基本上可以防止它在引擎盖下将 true | false 转换为 true & false,保留它的 boolean 性质。

现在它会正确说 UnionToIntersection<boolean>boolean,而不是 never,同时仍然正确说 UnionToIntersection<boolean | string>never