折叠一个有区别的联合——从联合派生出一个包含所有可能的键值组合的伞型

Mic*_*urz 5 generics union discriminated-union typescript

我有一个受歧视的工会,例如:

type Union = { a: "foo", b: string, c: number } | {a: "bar", b: boolean }
Run Code Online (Sandbox Code Playgroud)

我需要派生一个包含所有潜在属性的类型,分配有可能在 的任何成员上找到的类型Union,即使仅在某些成员上定义 - 在我的示例中:

type CollapsedUnion = { 
  a: "foo" | "bar", 
  b: string | boolean, 
  c: number | undefined 
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能制作一个派生出这种折叠联合的泛型?
我需要一个支持任何大小联合的泛型。

通过使用 native OmitUtility可以作为副产品实现类似的行为,但不幸的是,它遗漏了每个联合成员上不存在的属性(不将它们计入 asundefined或 via ?)。

Aro*_*ron 10

我找到了两种 方法

\n

编辑:这是一个具有两个单独类型参数的解决方案。请参阅下面的带有单个联合类型参数的解决方案。

\n
// The source types\ntype A = { a: "foo", b: string, c: number }\ntype B = { a: "bar", b: boolean }\n\n// This utility lets T be indexed by any (string) key\ntype Indexify<T> = T & { [str: string]: undefined; }\n\n// Where the magic happens \xe2\x9c\xa8\ntype AllFields<T, R> = { [K in keyof (T & R) & string]: Indexify<T | R>[K] }\n\ntype Result = AllFields<A, B>\n/**\n * \n * type Result = {\n *   a: "foo" | "bar";\n *   b: string | boolean;\n *   c: number | undefined;\n * }\n */\n
Run Code Online (Sandbox Code Playgroud)\n

怎么运行的

\n

AllFields是映射类型。映射类型的“key”部分

\n
[K in keyof (T & R) & string]\n
Run Code Online (Sandbox Code Playgroud)\n

意味着K扩展 union 的键,这意味着它将是 in或 inT & R的所有键的并集。这是第一步。它确保我们使用所有必需的键来创建一个对象。TR

\n

& string必需的,因为它指定K也必须是字符串。无论如何,情况几乎总是如此,因为 JS 中的所有对象键都是字符串(偶数) \xe2\x80\x93 除了符号之外,但无论如何,这些都是不同的鱼。

\n

类型表达式

\n
Indexify<T | R>\n
Run Code Online (Sandbox Code Playgroud)\n

T返回and的联合类型,但添加了字符串索引。这意味着,如果我们尝试对它进行索引,即使或 之一不存在,RTS 也不会抛出错误。KKTR

\n

最后

\n
Indexify<T | R>[K]\n
Run Code Online (Sandbox Code Playgroud)\n

意味着我们正在为这个 union-with-undefineds-for-string-indexes 建立索引K。如果是、或两者K的键,则将产生该键的值类型。TR

\n

否则,它将回退到[string]: undefined索引并导致值未定义。

\n

这是游乐场链接

\n
\n

编辑:单个通用参数的解决方案

\n

您指定实际上并不希望它适用于两个类型参数,而是适用于现有的联合类型,无论联合中有多少个成员。

\n

这需要血、汗和泪水,但我已经做到了。

\n
// Magic as far as I\'m concerned.\n// Taken from /sf/answers/3526270051/\ntype UnionToIntersection<U> =\n  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never\n\n// This utility lets T be indexed by any key\ntype Indexify<T> = T & { [str: string]: undefined; }\n\n// To make a type where all values are undefined, so that in AllUnionKeys<T>\n// TS doesn\'t remove the keys whose values are incompatible, e.g. string & number\ntype UndefinedVals<T> = { [K in keyof T]: undefined }\n\n// This returns a union of all keys present across all members of the union T\ntype AllUnionKeys<T> = keyof UnionToIntersection<UndefinedVals<T>>\n\n// Where the (rest of the) magic happens \xe2\x9c\xa8\ntype AllFields<T> = { [K in AllUnionKeys<T> & string]: Indexify<T>[K] }\n\n\n// The source types\ntype A = { a: "foo", b: string, c: number }\ntype B = { a: "bar", b: boolean; }\n\ntype Union = A | B\n\ntype Result = AllFields<Union>\n/**\n * \n * type Result = {\n *   a: "foo" | "bar";\n *   b: string | boolean;\n *   c: number | undefined;\n * }\n */\n
Run Code Online (Sandbox Code Playgroud)\n

UnionToIntersection从@jcalz 的精彩回答中得到了答案。我试图理解它,但无法理解。无论如何,我们可以将其视为一个将并集类型转换为交集类型的魔术盒。这就是我们获得想要的结果所需要的一切。

\n

新 TS 游乐场链接

\n