为什么我不能使用 switch 语句来缩小 Typescript 中的 class 类型?

Why can't I use a switch statement to narrow class types in Typescript?

Playground link with comments

这是使用 interfaces 缩小类型的标准示例。

// 2 types of entity
enum EntityType {
  ANIMAL = 'ANIMAL',
  PLANT = 'PLANT',
}

// animal has animal type and has legs attribute
interface Animal {
  entityType: EntityType.ANIMAL;
  legs: number;
}

//plant has plant type and has height attribute
interface Plant {
  entityType: EntityType.PLANT;
  height: number;
}

// generic entity is animal or plant
type Entity = Animal | Plant;

// operate on an entity
const doEntityThing = (entity: Entity) => {
  // can use type narrowing via switch based on entity.entityType
  switch(entity.entityType) {
    case EntityType.PLANT:
      return entity.height;
    case EntityType.ANIMAL:
      return entity.legs;
  }
};

在 switch 语句中,entity 的类型被缩小了,因为 entity 可以是的每个不同类型都有不同的 entityType,所以 TS 可以知道什么时候,比如说, entity.height是否有效

但现在这里有一个使用 classes 的类似示例:

// 2 types of foods
enum FoodType {
  MEAT = 'MEAT',
  VEG = 'VEG',
}

// base class for generic food
class FoodBase {
  public constructor(public foodType: FoodType){}
}

// instances of meat class have food type meat and have doneness attribute
class Meat extends FoodBase {
  public static foodType = FoodType.MEAT;
  public readonly foodType = Meat.foodType;

  public constructor(public doneness: 'rare' | 'burnt') {
    super(Meat.foodType);
  }
}

// instances of veg class have food type veg and have organic attribute
class Veg extends FoodBase {
  public static foodType = FoodType.VEG;
  public readonly foodType = Veg.foodType;

  public constructor(public organic: boolean) {
    super(Veg.foodType);
  }
}

// generic food is meat or veg
type Food = Meat | Veg;

// operate on a food
const doFoodThing = (food: Food) => {
  // can use instanceof to narrow the type of the food
  if(food instanceof Meat) {
    console.log(`This meat is ${food.doneness}.`);
  }
  else if(food instanceof Veg) {
    console.log(`This veg is${food.organic ? '' : ' not'} organic.`);
  }

  // can't use switch to narrow type! Why not?
  switch(food.foodType) {
    case FoodType.MEAT:
      console.log(food.doneness); // ERROR HERE!
      break;
    case FoodType.VEG:
      console.log(food.organic); // ERROR HERE!
      break;
  }
};

doFoodThing函数的参数是MeatVeg,两者具有不同的foodType属性。 Meat 总是有 foodType 'MEAT' 而 Veg 总是有 foodType 'VEG',所以不应该将参数的 foodType 缩小到 'MEAT' 意味着 food 参数应该有一个 doneness 属性吗?这似乎与上面示例中 entity 的缩小相同。实体参数是 AnimalPlant,并使用 switch 语句缩小参数的 entityType

这两种缩小情况有何不同?为什么一种有效而另一种无效?有没有一种方法可以使用 classes 执行此操作并且仍然能够使用 switch 语句?

这是因为 VegMeat 的 'foodType' 被推断为类型 FoodType。为了使其工作,您应该将 Veg 的 foodType 指定为 FoodType.VEG,将 Meat 的 foodType 指定为 FoodType.MEAT:

class Meat extends FoodBase {
  public static foodType = FoodType.MEAT as const;
  // Or like this: public static foodType: FoodType.MEAT = FoodType.MEAT;
}

class Veg extends FoodBase {
  public static foodType = FoodType.VEG as const;
  // or like this: public static foodType: FoodType.VEG = FoodType.VEG;
}