【问题标题】:How to use a switch statement to narrow down a type using conditional types?如何使用 switch 语句来缩小使用条件类型的类型?
【发布时间】:2021-09-29 03:27:46
【问题描述】:

我有一个 'Food' 对象,它可以是多种类型,具体取决于 'category' 属性的值。对象来自一个json,所以不可能知道之前的类型。

我正在尝试在 category 属性上使用 switch 语句,以便将 Food 对象转换为正确的类型


export type Category = 'fruit' | 'grain' | 'meat'

interface Food<IngredientCategory extends Category> {
  name: string;
  category: IngredientCategory
  [key: string]: string;
}

interface Fruit extends Food<'fruit'> {
  color: string;
}

interface Grain extends Food<'grain'>{
  size: string;
}

interface Meat extends Food<'meat'> {
  temperature: string
}

type FoodFromCategory<IngredientCategory extends Category> = IngredientCategory extends 'fruit' ? Fruit : IngredientCategory extends 'grain' ? Grain : Meat;

const castFood = <IngredientCategory extends Category>(category: IngredientCategory, food: Food<any>):
   Food<IngredientCategory> | undefined => {
  switch (category) {
    case "fruit":
      return food as Fruit
    case "grain":
      return food as Grain
    case "meat":
      return food as Meat
  }
  return undefined
};


这会在返回的行上产生错误:

TS2322:“水果”类型不能分配给“食物”类型。属性“类别”的类型不兼容。类型“水果”不能分配给类型“IngredientCategory”。 “fruit”可以分配给“IngredientCategory”类型的约束,但“IngredientCategory”可以用约束“Category”的不同子类型来实例化。

为什么 switch 语句不缩小“IngredientCategory”通用参数的范围?有没有其他方法可以做到这一点?

TS-Playground

【问题讨论】:

  • 因为Fruit 不是Food&lt;IngredientCategory&gt; 的子类型,所以不能赋值
  • 你想做的是上演员。这行不通。基本接口“食物”的返回将起作用。然后稍后使用“类别”来区分它们。
  • 您的意思是在某处使用FoodFromCategory 吗?因为你现在不是。

标签: typescript


【解决方案1】:

目前这在 TypeScript 中是不可能的。缩小category 类型的control flow analysis 不会缩小类型参数 IngredientCategory,因此编译器无法推断出Fruit 可分配给@ 987654332@即使category === "fruit"

这里要求解决方案的规范问题可能是microsoft/TypeScript#33912,如果实施,有一些特定的功能建议可能允许这样做,例如microsoft/TypeScript#33014


但是现在,编译器本身无法做到这一点,如果你想编译它,你需要做类似type assertion 的事情来告诉编译器它可以将(例如)Fruit 视为FoodFromCategory&lt;IngredientCategory&gt;:

const castFood = <IngredientCategory extends Category>(
  category: IngredientCategory, food: Food<any>
): FoodFromCategory<IngredientCategory> | undefined => {
  switch (category) {
    case "fruit":
      return food as Fruit as FoodFromCategory<IngredientCategory>
    case "grain":
      return food as Grain as FoodFromCategory<IngredientCategory>
    case "meat":
      return food as Meat as FoodFromCategory<IngredientCategory>
  }
  return undefined
};

这有效并抑制了错误。但请注意,这是您从编译器中承担验证类型安全的责任,并且您应该小心自己做对了。


请注意,在运行时,switch 语句无论如何都没有做任何有用的事情。 JavaScript 不知道FruitFoodFromCategory&lt;IngredientCategory&gt;;对于每种情况,所有这些都将作为 return food 发送到 JavaScript。如果你要去return food,不管category是什么,你还不如这样写castFood

const castFood = <IngredientCategory extends Category>(
  category: IngredientCategory, food: Food<any>
) => food as FoodFromCategory<IngredientCategory>;

这完全消除了switch 语句。它还消除了undefined,因为如果category 不在IngredientCategory 中,则无法调用castFood()

这就是所问问题的答案。退后一步,我有点担心您是否正在尝试在这里进行“投射”; category 参数在运行时根本不使用,所以它只是帮助编译器确定 food 应该是什么类型。但在这种情况下,您最好将Fruit | Grain | Meat 用作discriminated union 和公共category 属性,然后测试该属性,而不是将其作为单独的参数传入。

但如果不知道您打算如何或为什么要打电话给castFood(),我不会冒险朝那个方向冒险(但也许它看起来像this code)。相反,我只是重申通过控制流缩小泛型类型参数目前是不可能的,你需要使用类型断言或其他一些类型松散技术来编译它。

Playground link to code

【讨论】:

  • 谢谢。如果我想要一个函数而不是 castFood 函数,而是根据其类别返回 Food 对象的特定属性,该怎么办?该函数的参数需要是通用的以及返回值(因为它会根据成分类别而改变)。有没有办法不使用类型断言而使用 switch 语句?
  • 如果没有minimal reproducible example 我不能确定,但​​是如果没有类型断言,泛型和 switch 语句通常不起作用;您有时可以通过重构来避免这种情况,但同样,我需要查看一个可以使用的具体示例来提出任何具体建议。 (如果问题与原始问题不同,那么获得答案的最佳方法是发布新问题,而不是在 cmets 中。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-17
  • 2017-11-05
  • 2019-10-30
  • 2019-04-12
  • 1970-01-01
相关资源
最近更新 更多