简体   繁体   English

如何使用基于泛型类型的条件类型来描述约束?

[英]How to describe constraint using conditional type which is based on generic type?

I have a problem with typings in my code - union type constraint is generated instead of picking only one type from ItemProperties types dictionary.我在代码中输入时遇到问题 - 生成了联合类型约束,而不是仅从ItemProperties类型字典中选择一种类型。 Is there any solution to make strict constraint for props property inside ItemConfig based on current ItemType ?是否有任何解决方案可以根据当前ItemTypeItemConfig中的props属性进行严格约束?

PS wrapping T generic with tuple at props type declaration (as said here ) not solves the problem. PS 在props类型声明中用元组包装T generic(如此所述)不能解决问题。

Simplified code example:简化代码示例:

enum ItemType {
  Dog = 1,
  Car = 2,
  Building = 3,
}

interface ItemProperties {
  [Item.Dog]: {
    name: string;
  };
  [Item.Car]: {
    power: number;
  };
}

interface ItemConfig<T extends ItemType = ItemType> {
  type: T;
  props: T extends keyof ItemProperties ? ItemProperties[T] : {};
}

const config: ItemConfig[] = [
  {
    type: ItemType.Dog,
    props: ...
  }
];

Expected typeof props :预期typeof props

  { name: string }

Actual typeof props : typeof props实际类型:

  { name: string } | { power: number } | {}

As remarked in the comments, the issue is that ItemConfig is equal to {type: ItemType, props: {} | {name: string} | {power: number}}如评论中所述,问题在于ItemConfig等于{type: ItemType, props: {} | {name: string} | {power: number}} {type: ItemType, props: {} | {name: string} | {power: number}} {type: ItemType, props: {} | {name: string} | {power: number}} whereas you'll want it to be a discriminating union {type: ItemType.Dog, props: {name: string}} | {type: ItemType.Car, props: {power: number}} |.. {type: ItemType, props: {} | {name: string} | {power: number}}而你会希望它成为一个有区别的联合{type: ItemType.Dog, props: {name: string}} | {type: ItemType.Car, props: {power: number}} |.. {type: ItemType.Dog, props: {name: string}} | {type: ItemType.Car, props: {power: number}} |.. to type the array element props correctly. {type: ItemType.Dog, props: {name: string}} | {type: ItemType.Car, props: {power: number}} |..正确输入数组元素props

One way to create this union is by using a distributive conditional type ( docs ):创建此联合的一种方法是使用分布式条件类型 ( docs ):

type ItemConfig<T = ItemType> = T extends ItemType ? {
  type: T;
  props: T extends keyof ItemProperties ? ItemProperties[T] : {};
} : never

TypeScript playground TypeScript 操场

Because the T in the condition is a naked type parameter, ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building>因为条件中的T是一个裸类型参数, ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building> ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building> ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building> distributes to ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building> ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building>分发到ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building> ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building> ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building> , which is the desired union. ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building> ,这是所需的联合。

Alternatively (as captain-yossarian remarked), since ItemType extends PropertyKey (ie string | number | symbol ), you can use a mapped type to create an object that has the constituents of the desired union as its values, and index that object to obtain the union:或者(如 captain-yossarian 所说),由于ItemType扩展了PropertyKey (即string | number | symbol ),您可以使用映射类型创建一个 object ,它具有所需联合的成分作为其值,并索引该 object 以获得工会:

type ItemConfig = {
  [T in ItemType]: {
    type: T,
    props: T extends keyof ItemProperties ? ItemProperties[T] : {}
  }
}[ItemType]

TypeScript playground TypeScript 操场

This has the advantage that you don't need ItemConfig to have a generic parameter T , but it is limited to types that extend PropertyKey (otherwise you can't use it as a key in the mapped type).这样做的好处是您不需要ItemConfig具有通用参数T ,但它仅限于扩展PropertyKey的类型(否则您不能将其用作映射类型中的键)。

Both approaches approaches yield the same ItemConfig union, which will allow the appropriate props type to be inferred for each array element:这两种方法都会产生相同的ItemConfig联合,这将允许为每个数组元素推断出适当的props类型:

const config: ItemConfig[] = [
  {
    type: ItemType.Dog,
    props: {name: 'x'}
    // type of props: {name: string}
  },
  {
    type: ItemType.Car,
    props: {power: 7}
    // type of props: {power: number}
  },
  {
    type: ItemType.Dog,
    props: {power: 7} // Type error
    // type of props: {name: string}
  }
];

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM