TypeScript:将泛型接口作为其他接口的联合类型

4

我希望创建一个通用接口,其中包含表示其他接口属性的联合属性。

假设我有两个接口:

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

interface B {
    something: Array<string>;
}

我不想以以下方式编写C界面:

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

因为这意味着每当我修改接口A或B中的任何一个时,都需要手动修改接口C。根据我在TypeScript文档以及Stack Overflow上看到的答案,我应该声明一个新类型。
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
}

但是由于多个属性及其类型之间的不匹配,该方法失败了。

我将非常感激任何形式的帮助。谢谢。

3个回答

5
我认为以下版本的 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; }
// }

这与其他答案类似,它找到了T的所有组成部分的所有键的联合(称为UnionKeys,定义为T extends any ? keyof T:never),并返回一个映射类型,其中包含它们所有的内容。不同之处在于,这里我们还找到了T的所有组成部分的所有键的交集(称为IntersectKeys,仅定义为keyof T),并将键T拆分成两个键集。在交集中出现的那个存在于每个组成部分中,因此我们可以只做Pick<T,IntesectKeys>以获取共同属性。剩下的Exclude<UnionKeys, IntersectKeys>在最终类型中将是可选的。

更新2019-08-23:下面提到的错误似乎已经在TS3.5.1中修复了。

这相当丑陋,如果我对它感觉更好的话,我会把它清理干净。问题是,当任何出现在所有组成部分中的属性本身是可选的时,仍然存在问题。在TypeScript中有一个bug(截至TS3.5),在{a?:string} | {a?:number}中,a属性被视为像{a: string | number | undefined}一样的必需属性,而更正确的处理方式是如果任何组成部分将其作为可选,则将其视为可选。这个 bug 渗透到了MergeUnion

type Oops = MergeUnion<{a?: string} | {a?: number}>
// type Oops =  { a: string | number | undefined; }

我没有一个简单的答案,如果回答更复杂,那么我就到此为止。

也许这对你的需求足够了。 或者@TitianCernicova-Dragomir的答案对你的需求足够了。 希望这些答案能帮到你,祝你好运!

链接到代码


这完全解决了我的问题!非常感谢您的时间和努力,我非常感激。 - user11068505

3

既不能用交叉类型也不能用联合类型来达到 C。联合类型 (A | B) 只允许访问公共属性。交叉类型 (A & B) 将允许访问所有属性,但如果 AB 的属性不一致,则该属性将是两个属性的交集 (例如 something 将是 string & Array<string>;,在这里并不是特别有用)。

解决方案是构建一个自定义映射类型,该类型将从传递的所有类型中获取键,并从每个成员创建属性类型的联合:

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函数将接受一个T类型的参数,如果T是一个联合类型,它将返回所有成员类型的键名。这是使用条件分布类型的性质实现的。

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

这是必需的,因为对于联合类型 keyof 只返回公共成员。(keyof ({a : number} | { b: number })never)。

PropValue 还使用条件类型的分配律来提取键的所有值类型的并集。

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

将其放入映射类型中,我们得到Merge,它映射联合的每个成员中所有键,并映射到所有可能属性类型的联合。

看起来 OP 希望如果输入联合类型的所有成员中都不存在某个属性,则该属性在输出类型中是可选的。 - jcalz
1
@jcalz 嗯...没错...如果你有更好的版本,我不介意删除我的...我正在为明天的演讲做准备,最好不要在这个问题上拖延太久。 - Titian Cernicova-Dragomir
抱歉,正如@jcalz所述,如果输出类型在输入联合的所有成员中都不存在,则应将其设置为可选项,因此我接受了他的答案作为正确答案。非常感谢你花费时间和精力,因为这个解释非常准确,我一定会把它保存在某个地方!谢谢! - user11068505

1

感谢@jcalz提供的优秀答案!如果有人感兴趣,我已经修改了它以使其更易读。此外,提到的错误现在已经解决。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接