typescript 将并集类型转换为交集类型

lc8prwob  于 2022-11-18  发布在  TypeScript
关注(0)|答案(4)|浏览(156)

是否有方法将联合类型转换为交集类型:

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

我想对FunctionUnion应用一个转换以得到FunctionIntersection

ecr0jaav

ecr0jaav1#

你想从并集到交集吗?分布条件类型和条件类型的推理可以做到这一点。(对不起,不要认为有可能从交集到并集)下面是邪恶的魔法:

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

这样就可以将并集U重新打包成一个新的并集,其中所有的元素都处于逆变位置。这样就可以将类型推断为交集I,如手册中所述:
类似地,在反变位置中的相同类型变量的多个候选导致交集类型被推断。
让我们看看它是否有效。
首先,让我将您的FunctionUnionFunctionIntersection括起来,因为TypeScript似乎比函数return更紧密地绑定并集/交集:

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认为是实际联合的一些细节。

type Weird = UnionToIntersection<string | number | boolean>

变成了

type Weird = string & number & true & false

在TS3.6+中,它被急切地简化为

type Weird = never

因为不可能有string * 和number * 和true * 和false的值。
希望对你有帮助,祝你好运!

jmo0nnb3

jmo0nnb32#

还有一个非常相关的问题,当你想要一个几种类型的交集,但不一定要把联合转换成交集时。如果不求助于临时联合,就没有办法直接得到交集!
问题是我们想要得到交集的类型可能在内部有联合,这也将被转换为交集。

// 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 }

希望这也能展示一些其他有用的技术。

7jmck4yq

7jmck4yq3#

我稍微扩展了@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

6l7fqoea

6l7fqoea4#

你可以使用UnionToIntersection从一个辉煌的图书馆utility-types.(1.5M每周下载在2022/11)
或者来自另一个gem库type-fest的类似类型UnionToIntersection(2022/11年每周下载1.17亿次)。
使用方法如下:

import type { UnionToIntersection } from 'utility-types'
// or
import type { UnionToIntersection } from 'type-fest'

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

相关问题