为什么我不能使用 switch 语句来缩小 Typescript 中的 class 类型?
Why can't I use a switch statement to narrow class types in Typescript?
这是使用 interface
s 缩小类型的标准示例。
// 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
是否有效
但现在这里有一个使用 class
es 的类似示例:
// 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
函数的参数是Meat
或Veg
,两者具有不同的foodType
属性。 Meat 总是有 foodType 'MEAT' 而 Veg 总是有 foodType 'VEG',所以不应该将参数的 foodType 缩小到 'MEAT' 意味着 food 参数应该有一个 doneness
属性吗?这似乎与上面示例中 entity
的缩小相同。实体参数是 Animal
或 Plant
,并使用 switch 语句缩小参数的 entityType
。
这两种缩小情况有何不同?为什么一种有效而另一种无效?有没有一种方法可以使用 class
es 执行此操作并且仍然能够使用 switch
语句?
这是因为 Veg
和 Meat
的 '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;
}
这是使用 interface
s 缩小类型的标准示例。
// 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
是否有效
但现在这里有一个使用 class
es 的类似示例:
// 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
函数的参数是Meat
或Veg
,两者具有不同的foodType
属性。 Meat 总是有 foodType 'MEAT' 而 Veg 总是有 foodType 'VEG',所以不应该将参数的 foodType 缩小到 'MEAT' 意味着 food 参数应该有一个 doneness
属性吗?这似乎与上面示例中 entity
的缩小相同。实体参数是 Animal
或 Plant
,并使用 switch 语句缩小参数的 entityType
。
这两种缩小情况有何不同?为什么一种有效而另一种无效?有没有一种方法可以使用 class
es 执行此操作并且仍然能够使用 switch
语句?
这是因为 Veg
和 Meat
的 '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;
}