【发布时间】:2018-10-26 17:53:59
【问题描述】:
有没有办法将联合类型转换为交集类型:
type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void
我想对FunctionUnion 进行转换以获得FunctionIntersection
【问题讨论】:
标签: typescript
有没有办法将联合类型转换为交集类型:
type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void
我想对FunctionUnion 进行转换以获得FunctionIntersection
【问题讨论】:
标签: typescript
你想让联合交叉? Distributive conditional types 和 inference from conditional types 可以做到这一点。 (但不要认为可以进行交叉到联合,抱歉)这是邪恶的魔法:
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,所以
type Weird = UnionToIntersection<string | number | boolean>
变成
type Weird = string & number & true & false
在 TS3.6+ 中被急切地简化为
type Weird = never
因为不可能有string 和 number 和 true 和 false的值。
希望对您有所帮助。祝你好运!
【讨论】:
type Param<T> = T extends (arg: infer U) => void ? U : never; type InferredParams = Param<((a: string) => void) | ((a: number) => void)>; 应该给我string & number,但它给了我string | number。你能解释一下为什么吗?
extends 之前的裸类型参数在任何联合成分中都是distributed。如果要禁用分布式条件类型,可以使用使类型参数“穿衣”的技巧,例如像这样的单元素元组:type Param<T> = [T] extends [(arg: infer U) => void] ? U : never;。这应该按你想要的方式工作。
当您想要几种类型的交集但不一定将联合转换为交集时,还有一个非常相关的问题。如果不求助于临时工会,就无法直接到达十字路口!
问题是我们想要得到交集的类型可能在里面有联合,它们也会被转换为交集。救援守卫:
// 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 }
希望这也展示了一些其他有用的技术。
【讨论】:
我稍微扩展了@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
【讨论】: