Unique Playground Link with Comments
This is a standard illustration of type narrowing through the use of interface
s.
// Defining 2 types of entities
enum EntityType {
ANIMAL = 'ANIMAL',
PLANT = 'PLANT',
}
// The interface for animals, consisting of an animal type and legs attribute
interface Animal {
entityType: EntityType.ANIMAL;
legs: number;
}
// The interface for plants, consisting of a plant type and height attribute
interface Plant {
entityType: EntityType.PLANT;
height: number;
}
// A generic entity that can be either an animal or a plant
type Entity = Animal | Plant;
// Function to perform operations on an entity
const doEntityThing = (entity: Entity) => {
// Type narrowing is applied based on entity.entityType using a switch statement
switch(entity.entityType) {
case EntityType.PLANT:
return entity.height;
case EntityType.ANIMAL:
return entity.legs;
}
};
In this scenario, within the switch statement, the type of entity
is narrowed down because each possible type the entity
can be has a unique entityType
. This allows TypeScript to differentiate between valid and invalid references such as entity.height
.
Now, let's consider a similar example utilizing class
es:
// Defining 2 types of foods
enum FoodType {
MEAT = 'MEAT',
VEG = 'VEG',
}
// Base class for generic food
class FoodBase {
public constructor(public foodType: FoodType){}
}
// Class representing meat which includes 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);
}
}
// Class representing vegetables which includes 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 type that can be either meat or veg
type Food = Meat | Veg;
// Function to operate on a food item
const doFoodThing = (food: Food) => {
// Using instanceof to narrow down the food type
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.`);
}
// It is not feasible to use switch for type narrowing in this case
switch(food.foodType) {
case FoodType.MEAT:
console.log(food.doneness); // ERROR HERE!
break;
case FoodType.VEG:
console.log(food.organic); // ERROR HERE!
break;
}
};
The argument of the doFoodThing
function can be either a Meat
or a Veg
, both having distinct foodType
attributes. However, while it works well with a switch statement in one scenario, it encounters errors in the other. The primary difference between these two cases lies in how type instances are handled. Is there a way to achieve similar narrowing functionalities with classes and still utilize a switch statement effectively?