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

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

我在代码中输入时遇到问题 - 生成了联合类型约束,而不是仅从 ItemProperties 类型字典中选择一种类型。是否有任何解决方案可以根据当前 ItemTypeItemConfig 中对 props 属性 进行严格约束?

P. S.wrapping T generic with tuple at props type declaration (as said ) 不能解决问题。

简化代码示例:

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: ...
  }
];

预期 typeof props

  { name: string }

实际 typeof props:

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

如评论中所述,问题是 ItemConfig 等于 {type: ItemType, props: {} | {name: string} | {power: number}} 而您希望它是一个区分联合 {type: ItemType.Dog, props: {name: string}} | {type: ItemType.Car, props: {power: number}} | .. 来键入数组元素 props正确。

创建此联合的一种方法是使用分布式条件类型 (docs):

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

TypeScript playground

因为条件中的T是一个裸类型参数,所以ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building>分配给ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building>,也就是想要的并集

或者(如 captain-yossarian 所述),由于 ItemType 扩展了 PropertyKey(即 string | number | symbol),您可以使用映射类型创建一个对象,该对象具有所需联合的成分作为其值,并索引该对象以获得联合:

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

TypeScript playground

这样做的好处是你不需要ItemConfig有一个泛型参数T,但它仅限于扩展PropertyKey的类型(否则你不能将其用作映射类型中的键)。

两种方法都产生相同的 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}
  }
];