如何确保泛型可以采用泛型?

时间:2019-06-21 17:11:53

标签: typescript generics typescript-typings typescript-generics

道歉的标题,因为我不知道如何清楚地表达。
我正在尝试找到一种创建通用类型的方法,该方法稍后将用于传递通用类型。

要消除混乱,请考虑以下两个界面:

interface Field<T> {
  value: T;
  hasError: boolean;
}

interface Record {
  id: number;
  name: string;
}

Field的这些继承

interface FooField<T> extends Field<T> {
  foo: () => void;
}

interface BarField<T> extends Field<T> {
  bar: () => void;
}

我想生成以下类型的Record的新类型:

interface FooRecord {
  id: FooField<number>;
  name: FooField<string>;
}

interface BarRecord {
  id: BarField<number>;
  name: BarField<string>;
}

最直观但不正确的方法如下:

type MagicRecord<XField extends Field> = {
  [K in keyof Record]: XField<Record[K]>;
};

type FooRecord = MagicRecord<FooField>;
type BarRecord = MagicRecord<BarField>;

即使可能,我该如何正确使用打字稿?

1 个答案:

答案 0 :(得分:0)

您需要higher kinded types,从v3.5开始,TypeScript不支持。您在这里所做的任何事情都将是一种变通办法,其中涉及大量的代码重复。

这是一种可能的解决方法。首先,请注意,我不会使用名称Record,因为它会与built-in type alias的名称冲突。我将切换到MyRecord

interface MyRecord {
  id: number;
  name: string;
}

因此,我们有一个查找表FieldLookup<T>,它从通用类型的名称映射到由T指定的通用类型本身。您要支持的Field<T>的每种子类型都需要一个表:

interface FieldLookup<T> extends Record<keyof FieldLookup<any>, Field<T>> {
  Field: Field<T>;
  FooField: FooField<T>;
  BarField: BarField<T>;
}

然后我们可以定义MagicRecord<XField>,它将通用字段的名称作为字符串文字:

type MagicRecord<XField extends keyof FieldLookup<any>> = {
  [K in keyof MyRecord]: FieldLookup<MyRecord[K]>[XField]
};

type FooRecord = MagicRecord<"FooField">;
type BarRecord = MagicRecord<"BarField">;

所以可行。通过对FieldLookup进行反向查找,我们可以做得更进一步,以使您无需使用或关心类型名称:

type ReverseLookup<F extends Field<any>> = {
  [K in keyof FieldLookup<any>]: FieldLookup<any>[K] extends F ? K : never
}[keyof FieldLookup<any>];

然后MagicRecord<XField>将用作您的原始版本,除了XField必须是具体类型:

type MagicRecord2<XField extends Field<any>> = MagicRecord<
  ReverseLookup<XField>
>;

type FooRecord2 = MagicRecord2<FooField<any>>; // FooField<any>, not FooField
type BarRecord2 = MagicRecord2<BarField<any>>; // BarField<any>, not BarField

这也可以。

好的,希望能有所帮助。祝你好运!

Link to code