如何保证泛型可以取泛型?

How to ensure that a generic type can take a generic type?

抱歉标题混乱,因为我不知道如何表达清楚。
我正在尝试实现一种创建泛型类型的方法,稍后将使用它来传递泛型类型。

为了消除混淆,给定这两个接口:

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

我怎样才能做到这一点,如果可能的话,正确地使用打字稿?

你想要 higher kinded types,从 v3.5 开始,TypeScript 不支持它。您在这里所做的任何事情都将是一种解决方法,涉及一定数量的代码重复。

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

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

所以让我们查找 table FieldLookup<T>,它从通用类型的名称映射到用 T 指定的通用类型本身。此 table 需要为您要支持的 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