TypeScript:将通用接口作为其他接口的并集 [英] TypeScript: generic interface as union of other interfaces

查看:264
本文介绍了TypeScript:将通用接口作为其他接口的并集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个具有表示其他接口的属性并集的属性的通用接口.

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;
}

因为这意味着每当我修改接口AB时,我也需要手动修改接口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<T>可能会表现您想要的样子:

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,定义为T extends any ? keyof T : never),并返回一个映射类型,其中所有元素都位于它.不同之处在于,在这里我们还找到了T的所有成分的所有键的交集(将其称为IntersectKeys,仅定义为keyof T),并将键T分为两组键.每个组成部分中都存在相交处的一个,因此我们只需执行Pick<T, IntesectKeys>即可获得公共属性.其余的>在最终类型中将是可选的.

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.

更新2019-08-23:自TS3.5.1起,以下提到的错误似乎已修复

UPDATE 2019-08-23: the bug mentioned below seems to be fixed as of TS3.5.1

这很丑陋,如果感觉更好,我会清理它.问题是,出现在所有组成部分中的任何属性本身都是可选的时,仍然存在问题.在{a?: string} | {a?: number}中,有一个 TypeScript错误(自TS3.5起), a属性被视为像{a: string | number | undefined}这样的 required 属性,而如果任何组成部分将其视为可选,则将其视为可选是更正确的.该错误会流到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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆