首页 > 解决方案 > 如何在 TypeScript 中定义一组通用对象,每个项目都有不同的模板参数

问题描述

我正在尝试为表单定义一个字段数组,其中每个项目可能具有不同的类型。我已经定义了这些类型:

  interface FormData{
    value1:number
    value2:number|null
    value3:string
    value4:boolean
  }

  interface Field<T,K extends keyof T,TV=T[K]>{
    key:K&string
    value:TV
    validation?:(value:TV)=>boolean
  }

定义单个字段可以正常工作:

  const field:Field<FormData,'value1'>={
    key:'value1',
    value:1,
    validation(value:number):boolean{
      return value<3
    }
  }

但是当为多个项目定义一个数组时,像这样:

  const fields:Field<FormData,keyof FormData>[]=[
    {
      key:'value1',
      value:1,
      validation(value:number):boolean{
        return value<3
      }
    },
    {
      key:'value3',
      value:'xxx',
      validation(value:string):boolean{
        return value!=='xxx'
      }
    }
  ]

打字稿是:

  1. 允许value是任何类型,只要它是number|null|string|undefined(例如字符串value1
  2. 抱怨这些validation()功能,因为它们不接受number|null|string|undefined作为参数

有没有办法帮助 TS 推断数组中每个项目的正确类型?理想情况下,我还想将数组定义为Field<FormData>[].

我正在使用 TypeScript 4.3。

标签: typescriptgenerics

解决方案


如果您继续在键中使您的类型Field<T, K> 通用K,那么您确实会发现Field<T, ??>为不同的键表示一个数组很烦人K。有很多方法可以使用映射元组来做到这一点,但实际上,这需要您指定或让编译器推断特定的键类型数组。但是你并不关心使用了哪些键,只要它们都是一些实际的键。

这是存在量化泛型的规范用例,TypeScript 不直接支持。(在microsoft/TypeScript#14466上有一个请求,但现在这是一个缺失的功能。)TypeScript 的泛型,就像大多数语言的泛型一样,是普遍量化的,您可以将其视为类型的无限交集。另一方面,存在量化的泛型充当类型的无限联合

但是等等,你的FormData接口,像大多数对象类型一样,有一个有限的键列表。你不需要无限的联合;你可以只使用一个工会!Field<FormData, K>意思是,为每个Kin建立一个原始类型的联合keyof FormData。这是定义它的一种方法:

  type Field<T> = { [K in Extract<keyof T, string>]-?: {
    key: K
    value: T[K],
    validation?: (value: T[K]) => boolean
  } }[Extract<keyof T, string>];

这会为 中的每个键构建一个具有您想要的字段类型的映射类型,然后立即索引到该映射类型以获取其属性的联合。Kkeyof T

您可以看到它使您需要的类型:

  type FieldFormData = Field<FormData>;

  /* type FieldFormData = {
    key: "value1";
    value: number;
    validation?: ((value: number) => boolean) | undefined;
} | {
    key: "value2";
    value: number | null;
    validation?: ((value: number | null) => boolean) | undefined;
} | {
    key: "value3";
    value: string;
    validation?: ((value: string) => boolean) | undefined;
} | {
    ...;
} */

现在类型中没有明确K提及,您可以使用Array<Field<FormData>>

const fields: Field<FormData>[] = [
  {
    key: 'value1',
    value: 1,
    validation(value: number): boolean {
      return value < 3
    }
  },
  {
    key: 'value3',
    value: 'xxx',
    validation(value: string): boolean {
      return value !== 'xxx'
    }
  }
]

它还会捕获错误:

const badFields: Field<FormData>[] = [
  { key: "value2", value: "oops" }, // error!
  { key: "value4", value: true } // okay
]

Playground 代码链接


推荐阅读