TypeScript:通用接口作为其他接口的联合 [英] TypeScript: generic interface as union of other interfaces
问题描述
我想创建一个通用接口,其属性代表来自其他接口的属性的联合.
I would like to create a generic interface with properties that represent a union of properties from other interfaces.
假设我有两个接口
interface A {
something: string;
somethingElse: number;
}
interface B {
something: Array<string>;
}
我不想把接口C
写成
interface C {
something: string | Array<string>;
somethingElse?: number;
}
因为这意味着每当我修改接口 A
或 B
时,我也需要手动修改接口 C
.
because that would mean that whenever I modify either of the interfaces A
or B
, I would need to manually modify interface C
as well.
根据我在 TypeScript 文档中看到的以及 Stack Overflow 上的答案,我应该声明一个新类型
From what I've seen in the TypeScript documentation as well as answers here on Stack Overflow, I should declare a new type
type unionOfKeys = keyof A | keyof B;
并实现通用接口形式
interface GenericInterface {
<T>(arg: T): T;
}
我正在考虑
interface C {
<T extends unionOfKeys>(arg: T): T extends unionOfKeys ? A[T] | B[T] : any
}
但由于许多属性与其类型不匹配而失败.
but that fails because of mismatch between a number of properties and their types.
我将不胜感激.谢谢.
推荐答案
我认为以下版本的 MergeUnion
可能表现得如您所愿:
I think the following version of MergeUnion<T>
might behave how you want:
type MergeUnion<T> = (
keyof T extends infer K ? [K] extends [keyof T] ? Pick<T, K> & {
[P in Exclude<(T extends any ? keyof T : never), K>]?:
T extends Partial<Record<P, infer V>> ? V : never
} : never : never
) extends infer U ? { [K in keyof U]: U[K] } : never;
type C = MergeUnion<A | B>;
// type C = {
// something: string | string[];
// somethingElse?: number | undefined; }
// }
这类似于另一个答案,因为它找到了 T
的所有组成部分的所有键的并集(称之为 UnionKeys
,定义为 Textends any ? keyof T : never
) 并返回一个包含所有这些的映射类型.不同的是,这里我们还找到了T
的所有成分的所有key的交集(称之为IntersectKeys
,定义为只是keyof T
) 并将键 T
拆分为两组键.来自交集的一个存在于每个组成部分中,因此我们只需执行 Pick
即可获取公共属性.余数 Exclude
This is similar to the other answer in that it finds the union of all keys of all the constituents of T
(call it UnionKeys
, defined as T extends any ? keyof T : never
) and returns a mapped type with all of them in it. The difference is that here we also find the intersection of all keys of all the constituents of T
(call it IntersectKeys
, defined as just keyof T
) and split the keys T
into two sets of keys. The one from the intersection are present in every constituent, so we can just do Pick<T, IntesectKeys>
to get the common properties. The remainder, Exclude<UnionKeys, IntersectKeys
> will be optional in the final type.
UPDATE 2019-08-23:下面提到的错误似乎从 TS3.5.1 开始修复
UPDATE 2019-08-23: the bug mentioned below seems to be fixed as of TS3.5.1
它很丑,如果我感觉好点我会清理它.问题是当出现在所有成分中的任何属性本身都是可选的时,仍然存在问题.TypeScript 中的错误(从 TS3.5 开始)在 {a?: 字符串} |{a?: number}
,a
属性被视为必需 属性,如 {a: string |数量 |undefined}
,而如果任何组成部分将其视为可选,则将其视为可选会更正确.该错误渗透到 MergeUnion
:
It's pretty ugly, and I'd clean it up if I felt better about it. The problem is that there's still an issue when any of the properties appearing in all constituents are themselves optional. There's a bug in TypeScript (as of TS3.5) where in {a?: string} | {a?: number}
, the a
property is seen as a required property like {a: string | number | undefined}
, whereas it would be more correct to be treated as optional if any of the constituents have it as optional. That bug bleeds through to MergeUnion
:
type Oops = MergeUnion<{a?: string} | {a?: number}>
// type Oops = { a: string | number | undefined; }
我没有更好的答案而不是更复杂的,所以我就到此为止.罢工>
I don't have a great answer there that isn't even more complicated, so I'll stop here.
也许这足以满足您的需求.或者@TitianCernicova-Dragomir 的回答可能足以满足您的需求.希望这些答案对您有所帮助;祝你好运!
Maybe this is sufficient for your needs. Or maybe @TitianCernicova-Dragomir's answer is sufficient for your needs. Hope these answers help you; good luck!
这篇关于TypeScript:通用接口作为其他接口的联合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!