如何使用 switch 语句使用条件类型来缩小类型范围?

Sim*_*ran 5 typescript

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

\n

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

\n
\nexport type Category = \'fruit\' | \'grain\' | \'meat\'\n\ninterface Food<IngredientCategory extends Category> {\n  name: string;\n  category: IngredientCategory\n  [key: string]: string;\n}\n\ninterface Fruit extends Food<\'fruit\'> {\n  color: string;\n}\n\ninterface Grain extends Food<\'grain\'>{\n  size: string;\n}\n\ninterface Meat extends Food<\'meat\'> {\n  temperature: string\n}\n\ntype FoodFromCategory<IngredientCategory extends Category> = IngredientCategory extends \'fruit\' ? Fruit : IngredientCategory extends \'grain\' ? Grain : Meat;\n\nconst castFood = <IngredientCategory extends Category>(category: IngredientCategory, food: Food<any>):\n   Food<IngredientCategory> | undefined => {\n  switch (category) {\n    case "fruit":\n      return food as Fruit\n    case "grain":\n      return food as Grain\n    case "meat":\n      return food as Meat\n  }\n  return undefined\n};\n\n\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

TS2322:类型“Fruit”不可分配给类型“Food”。\xc2\xa0\xc2\xa0属性\'category\'的类型不兼容。\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0Type \'"fruit"\' 不可分配给类型 \'IngredientCategory\'。\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\'"fruit"\' 可分配给类型为 \'IngredientCategory\' 的约束,但 \'IngredientCategory\'可以用约束“类别”的不同子类型来实例化。

\n
\n

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

\n

TS-游乐场

\n

jca*_*alz 8

目前这在 TypeScript 中是不可能的。缩小类型的控制流分析category不会缩小类型参数 IngredientCategory,因此编译器无法推断出可分配FruitFoodFromCategory<IngredientCategory>Even if category === "fruit"

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


但目前,编译器本身无法做到这一点,如果您希望编译它,您将需要执行类似类型断言之类的操作来告诉编译器它可以将(例如)Fruit视为FoodFromCategory<IngredientCategory>

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
};
Run Code Online (Sandbox Code Playgroud)

这有效并抑制了错误。但请注意,这是您将验证类型安全的责任从编译器中移开,并且您应该小心确保正确执行此操作。


请注意,在运行时, switch 语句无论如何都没有做任何有用的事情。JavaScript 不知道Fruitor FoodFromCategory<IngredientCategory>;对于每种情况,所有这些都会被发送到 JavaScript return food。如果你不管怎样return foodcategory你不妨castFood这样写:

const castFood = <IngredientCategory extends Category>(
  category: IngredientCategory, food: Food<any>
) => food as FoodFromCategory<IngredientCategory>;
Run Code Online (Sandbox Code Playgroud)

这完全消除了该switch声明。它还消除了undefined,因为没有有效的方法来调用castFood()if categoryis not in IngredientCategory

这就是所问问题的答案。退一步来说,我有点担心你是否试图在这里进行“转换”;该category参数在运行时根本不使用,因此它只是帮助编译器确定food应该是什么类型。但在这种情况下,您可能最好将其用作公共属性的可区分联合Fruit | Grain | Meat,然后测试该属性,而不是将其作为单独的参数传递。category

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

Playground 代码链接