如何在 ts 中单独操作 union 中的每个元素?
如果我们的 union is assignable to PropertyKey
,那么将会非常简单,我们可以在 mapping type 中单独操作他们:
type iterate<U extends PropertyKey> = {
[key in U]: key // mapping to anything you want
}
但若 union 中存在额外情况,那就需要使用其他办法了。
将 union 拆解开来并单独访问,我们知道 conditional type 有这个能力,因为 union 在其中会被拆解并分别 distribute 到单独的表达式中,但可惜的是,在运算结束后,这些值最终仍然会被 union 到一起,这个行为我们无法控制。
此外,依托函数重载的某些特性,也可以对 union 的元素进行单独访问,这种方法有点 hack,先看这个例子:
declare function fun(): 'a'
declare function fun(): 'b'
type test = ReturnType<typeof fun> // 'b'
对重载的函数,推断其函数签名时总以最后一个为准。
When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types.
typescript2.8 release notes
而函数类型的交集在该行为上与函数重载一致,即:
type fun = (() => 'a') & (() => 'b')
type test = ReturnType<fun> // 'b'
既然如此,我们可以将 union type 的每一个元素映射到"带有该元素类型返回值(或参数也可以)的函数类型",然后取个交集即可。
type ToFun<U> = U extends any ? () => U : never; // (() => 'a') | (() => 'b')
然后取交集:
type U2I<U> = (U extends any ? (u: U) => any : never) extends (i: infer I) => any ? I : never
type Intersection<U> = U2I<ToFun<U>> // (() => 'a') & (() => 'b')
然后我们来开始尝试操作每个元素:
type LastOfUnion<U> = Intersection<U> extends () => infer R ? R /* the last of the overload */: never
type iterate<U> = LastOfUnion<U> extends never ? never : /* do sth */ iterate<Exclude<U, LastOfUnion<U>>>
通过将当前的 last 元素 exclude 出去,然后进入下一层递归,我们就可以在一个 union 上迭代了。
一个例子是 type-challengs/union-to-tuple,尝试一下!