泛型的打字稿图

时间:2020-10-18 00:00:22

标签: typescript generics

我试图让Field中的每个ObjectType拥有自己的通用名称,因此每个Field的resolve fn必须返回在type属性中定义的类型

type Scalars = {
  ID: string;
  Int: number;
  Float: number;
  String: string;
  Boolean: boolean;
};

type Field<T extends keyof Scalars> = {
  type: T,
  resolve: () => Scalars[T];
}

type ObjectType<T extends keyof Scalars> = {
  [key: string]: Field<T>
}

const objectType = <T extends keyof Scalars>(config: ObjectType<T>): any => 'todo';

objectType({
  id: {
    type: 'Int',
    // type of `resolve` is `() => (string | number)`, should be `() => number`
    resolve: () => '123',
  },
  name: {
    type: 'String',
    // should be `() => string`
    resolve: () => 123,
  },
});

研究了其他一些SO问题之后,我在https://stackoverflow.com/a/51547767/11042710

中找到了几乎可以解决我的问题的解决方案

这是我尝试上面建议的TS游乐场 TS playground

但是当我尝试将infer P类型传递给extends keyof Scalars的泛型时失败了

1 个答案:

答案 0 :(得分:2)

您的Field<..>类型看起来可以像这样形成discriminated union

type Fields = { [K in keyof Scalars]: Field<K> }[keyof Scalars];

这里Fields的类型等同于

type Fields = Field<"ID"> | Field<"Int"> | Field<"Float"> | 
  Field<"String"> | Field<"Boolean">

其中,联合中的所有术语都有一个公用的type字段,该字段的字符串文字类型可用于区分它们。很好,因为这意味着您的objectType()方法可以减少很多通用的跳转,因为config属性值可以限制为特定的类型Fields

const objectType = <T extends Record<keyof T, Fields>>(config: T): any => 'todo';

现在我们要做的就是将config约束为具有任何键(Record<keyof T, ...>)且其值均为Fields的对象。让我们看看它是否有效。这是预期的用途:

objectType({
  id: {
    type: 'String',
    resolve: () => "123",
  },
  ide: {
    type: 'Int',
    resolve: () => 123,
  },
});

这是您做错了会发生的事情:

objectType({
  id: { // error! number is not assignable to string
    type: 'String',
    resolve: () => 123,
  },
  ide: { // error! string is not assignable to boolean
    type: 'Boolean',
    resolve: () => "123",
  },
});

那是您期望的错误,对吧?

Playground link to code