Here’s a common problem: a value can belong to one (and only one) of a number of possible categories. We need to write code that does something different for each possible category.
Often, the code to handle such a problem is implemented like below:
enum MailLocation {
UNITED_STATES = 'UNITED_STATES',
CANADA = 'CANADA'
}
const getLocationPolicy = (location: MailLocation) => {
if (location === Location.UNITED_STATES) {
return { shippingType: 'truck', price: 9 }
} else if (location === Location.CANADA) {
return { shippingType: 'air', price: 12 }
}
}
This certainly works, but may not be our best path forward if we can ever expect the number of possible values of a location to grow. When a new location is added, we don’t get any compile-time checking to ensure we’ve considered it.
Let’s look at how we can use a mapping object to enforce that all possible location values have been considered.
enum MailLocation {
UNITED_STATES = 'UNITED_STATES',
CANADA = 'CANADA'
}
interface Policy {
shippingType: 'truck' | 'air';
price: number;
}
const getLocationPolicy = (location: MailLocation): Policy => {
const locationToPolicyMapping: { [l in MailLocation]: Policy } = {
[MailLocation.UNITED_STATES]: { shippingType: 'truck', price: 9 },
[MailLocation.CANADA]: { shippingType: 'air', price: 12 }
};
return locationToPolicyMapping[location];
}
Now, when we add a new possible MailLocation
:
enum MailLocation {
UNITED_STATES = 'UNITED_STATES',
CANADA = 'CANADA',
MEXICO = 'MEXICO'
}
interface Policy {
shippingType: 'truck' | 'air';
price: number;
}
const getLocationPolicy = (location: MailLocation): Policy => {
// Property 'MEXICO' is missing in type
const locationToPolicyMapping: { [l in MailLocation]: Policy } = {
[MailLocation.UNITED_STATES]: { shippingType: 'truck', price: 9 },
[MailLocation.CANADA]: { shippingType: 'air', price: 12 }
};
return locationToPolicyMapping[location];
}
We’ve used a mapped type to annotate the mapping object. This enforces that there will exist a key for each entry in our MailLocation
enum.