简体   繁体   English

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.我想创建一个通用接口,其属性表示来自其他接口的属性的联合。

Let's say I have two interfaces假设我有两个接口

interface A {
    something: string;
    somethingElse: number;
}

interface B {
    something: Array<string>;
}

I do not want to write interface C as我不想将接口C写为

interface C {
    something: string | Array<string>;
    somethingElse?: number;
}

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.因为这意味着每当我修改接口AB ,我也需要手动修改接口C

From what I've seen in the TypeScript documentation as well as answers here on Stack Overflow, I should declare a new type从我在 TypeScript 文档中看到的以及 Stack Overflow 上的答案来看,我应该声明一个新类型

type unionOfKeys = keyof A | keyof B;

and implement generic interface form并实现通用接口形式

interface GenericInterface {
    <T>(arg: T): T;
}

I was thinking in the direction of我在思考的方向

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.但由于许多属性与其类型不匹配而失败。

I would appreciate any sort of help.我将不胜感激任何形式的帮助。 Thank you.谢谢你。

I think the following version of MergeUnion<T> might behave how you want:我认为以下版本的MergeUnion<T>可能表现得如你所愿:

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

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.这与另一个答案类似,因为它找到T的所有组成部分的所有键的并集(称为UnionKeys ,定义为T extends any ? keyof T : never )并返回一个映射类型,其中包含所有这些。 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.不同的是,这里我们还找到了T的所有组成部分的所有键的IntersectKeys (称为IntersectKeys ,定义为keyof T )并将键T拆分为两组键。 The one from the intersection are present in every constituent, so we can just do Pick<T, IntesectKeys> to get the common properties.来自交集的一个存在于每个组成部分中,因此我们可以只执行Pick<T, IntesectKeys>来获取公共属性。 The remainder, Exclude<UnionKeys, IntersectKeys > will be optional in the final type.余数Exclude<UnionKeys, IntersectKeys > 在最终类型中是可选的。

UPDATE 2019-08-23: the bug mentioned below seems to be fixed as of TS3.5.1更新 2019-08-23:下面提到的错误似乎从 TS3.5.1 开始修复

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} TypeScript 中有一个 错误(从 TS3.5 开始),其中 {a?: string} | {a?: number} {a?: string} | {a?: number} , the a property is seen as a required property like {a: string | number | undefined} {a?: string} | {a?: number}a属性被视为 必需属性,如 {a: string | number | undefined} {a: string | number | undefined} {a: string | number | undefined} , whereas it would be more correct to be treated as optional if any of the constituents have it as optional. {a: string | number | undefined} ,而如果任何成分将其视为可选,则将其视为可选会更正确。 That bug bleeds through to MergeUnion : 该错误渗透到 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. 我没有一个很好的答案,没有更复杂的,所以我会停在这里。

Maybe this is sufficient for your needs.也许这足以满足您的需求。 Or maybe @TitianCernicova-Dragomir's answer is sufficient for your needs.或者@TitianCernicova-Dragomir 的回答可能足以满足您的需求。 Hope these answers help you;希望这些答案对您有所帮助; good luck!祝你好运!

Link to code 代码链接

Neither intersection types or union types will get us to C .交集类型或联合类型都不会让我们进入C A union type ( A | B will only allow access to common properties).联合类型( A | B将只允许访问公共属性)。 An intersection ( A & B ) will allow access to all properties but if the properties disagree between A and B the property will be an intersection of the two properties ( ex something will be string & Array<string>; which is not very useful here).交集( A & B )将允许访问所有属性,但如果AB之间的属性不一致,则该属性将是两个属性的交集(例如, something将是string & Array<string>;这在这里不是很有用)。

The solution is to build a custom mapped type that will take keys from all types passed in and create a union of property types from each member:解决方案是构建一个自定义映射类型,该类型将从传入的所有类型中获取键并从每个成员创建属性类型的联合:

interface A {
    something: string;
    somethingElse: number;
}

interface B {
    something: Array<string>;
}

type KeyOf<T> = T extends any ? keyof T : never;
type PropValue<T, K extends PropertyKey> = T extends Record<K, infer V> ? V : never;
type Merge<T> = {
    [P in KeyOf<T>] : PropValue<T, P>
}

type C = Merge<A | B>
// type C = {
//     something: string | string[];
//     somethingElse: number;
// }

KeyOf will take a T and if T is a union it will return keys of all union members. KeyOf将接受一个T ,如果T是一个联合,它将返回所有联合成员的键。 It does this using the distributive property of conditional types它使用条件类型的分配属性来做到这一点

type K = KeyOf<{a : number} | { b: number }> //  "a" | "b". 

This is needed as keyof for a unuion will only return common members.这是必要的,因为keyof的 keyof 只会返回普通成员。 ( keyof ({a : number} | { b: number }) is never ). keyof ({a : number} | { b: number }) never )。

PropValue also uses the distributive property of conditional types to extract a union of all value types for a key. PropValue还使用条件类型的分布属性来提取键的所有值类型的联合。

type V = PropValue<{a : number} | {a : string} |{ b: number }, 'a'> //  string | number

Putting it together in a mapped type we get Merge which maps over all keys in every member of the union and maps to a union of all possible property types.把它放在一个映射类型中,我们得到了Merge ,它映射了联合的每个成员中的所有键,并映射到所有可能的属性类型的联合。

Thanks, @jcalz, for the great answer!谢谢@jcalz 的好答案! I've modified it for better readability if anyone's interested.如果有人感兴趣,我已经对其进行了修改以提高可读性。 Also, the mentioned bug is now solved.此外,上述错误现已解决。

type Keys<TUnion> =
  TUnion extends unknown ? keyof TUnion : never;

type Values<TObject extends Object> = {
  [TKey in keyof TObject]: TObject[TKey];
};

//

type RequiredKeys<TUnion> =
  keyof TUnion;

type RequiredValues<TUnion> =
  Pick<TUnion, RequiredKeys<TUnion>>;

//

type OptionalKeys<TUnion> =
  Exclude<Keys<TUnion>, RequiredKeys<TUnion>>;

type OptionalValue<TUnion, TKey extends PropertyKey> =
  TUnion extends Partial<Record<TKey, infer TValue>> ? TValue : never;

type OptionalValues<TUnion> = {
  [TOptionalKey in OptionalKeys<TUnion>]?: OptionalValue<TUnion, TOptionalKey>;
};

//

export type Merge<TUnion> = Values<
  RequiredValues<TUnion> &
  OptionalValues<TUnion>
>;


type Test = Merge<
  | { a?: string; b: string; c: number; }
  | { a?: number; b: string[]; c?: number; }
>;

// type Test = {
//   a?: string | number | undefined;
//   b: string | string[];
//   c?: number | undefined;
// };

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM