您请求的操作非常接近于identity操作;如果您有一个union type,就像A | B
一样,并访问其属性,则每个属性都将自动成为并集.如果这就是您想要的全部,那么您可以按原样使用联合,或者如果您想要的话,编写一个mapped type来将联合压缩为单一的对象类型.
这里唯一的问题是,您希望只出现在某些联盟成员中的属性在组合类型中为optional,而联盟往往会忽略这些属性.因此,我们应该做的第一件事是将联合的每个成员转换为一个新类型,对于未在该成员中定义的任何属性,具有类型never
的可选属性.也就是说,我们想把{a: 0, b: 1} | {b: 2, c: 3}
变成类似{a: 0, b: 1, c?: never} | {a?: never, b: 2, c: 3}
的东西.然后,当合并时,它们将按照需要合并成{a?: 0, b: 1 | 2, c?: 3}
.
把这些放在一起看起来是这样的:
type _Combine<T, K extends PropertyKey = T extends unknown ? keyof T : never> =
T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
type Combine<T> = { [K in keyof _Combine<T>]: _Combine<T>[K] }
这里的_Combine<T>
是一个帮助器类型,它使用distributive conditional types将T
拆分成联合成员并对它们执行操作.第一个是K
的default type argument,它收集T
(T extends unknown ? keyof T : never
)成员的所有键,第二个是intersect,其中T
的每个成员都有一个对象,对于K
中不属于T
的成员的每个键,都有一个never
类型的可选键.
然后Combine<T>
是_Combine<T>
之上的身份映射类型,它将({a: 0, b: 1) & Partial<Record<"c", never>>) | ({b: 2, c: 3} & Partial<Record<"a", never>>)
等难看的东西折叠为所需的{a?: 0, b: 1 | 2, c?: 3}
.
让我们在你的例子中测试一下:
type O = {
type: "bar";
foo: string;
bar: number;
} | {
type: "baz";
foo: string;
baz: boolean;
}
type Z = Combine<O>;
/* type Z = {
type: "bar" | "baz";
foo: string;
bar?: number | undefined;
baz?: boolean | undefined;
} */
看上go 不错.type
和foo
属性是必需的,因为它们存在于O
的所有成员中,而bar
和baz
属性是可选的,因为它们至少在O
的一个成员中缺失.
Playground link to code个