Literal Unions
Many values in a game come from a short, fixed list. A hero is a warrior, a mage, or a ranger; on the map, you go north, south, east, or west. As a text, such a value is a string, but a string may be any text, typos included:
function startingHealth(heroClass: string): number {
if (heroClass === "warrior") {
return 120;
} else if (heroClass === "mage") {
return 70;
} else {
return 90;
}
}
console.log(startingHealth("wariror"));This prints 90, the health of a ranger, because "wariror" isn't "warrior". There's no error, just a wrong number somewhere in the game, and you may search for the cause for a long time.
In the Basic Types lesson, you saw that a literal type such as "hard" allows exactly one value. With |, you can join several literal types into a literal union, and give it a name with type:
type HeroClass = "warrior" | "mage" | "ranger";A HeroClass is one of these three texts and nothing else. Put this line at the top of the program above, change the parameter to heroClass: HeroClass, and TypeScript checks every call:
main.ts(13,28): error TS2345: Argument of type '"wariror"' is not assignable to parameter of type 'HeroClass'.The quotes inside the quotes mean the exact text "wariror", as you know from literal types. The same protection works everywhere the type is used. For a variable, TypeScript even suggests what you probably meant:
type HeroClass = "warrior" | "mage" | "ranger";
let chosen: HeroClass = "wariror";main.ts(3,5): error TS2820: Type '"wariror"' is not assignable to type 'HeroClass'. Did you mean '"warrior"'?And it checks comparisons too. If the typo were in the function, in if (heroClass === "wariror"), TypeScript would report:
main.ts(4,9): error TS2367: This comparison appears to be unintentional because the types 'HeroClass' and '"wariror"' have no overlap.“No overlap” means that no value is both a HeroClass and the text "wariror", so the comparison could never be true.
One thing may surprise you. When the class is in a let variable without a type, the call is an error, even though "mage" is a class. With these two lines below the function:
let choice = "mage";
console.log(startingHealth(choice));main.ts(14,28): error TS2345: Argument of type 'string' is not assignable to parameter of type 'HeroClass'.TypeScript inferred the type string for choice, as it does for every let with a text: later, you could store any other text in it. Either give the variable the type, let choice: HeroClass = "mage";, or use const: the type of const choice = "mage" is the literal type "mage", which fits.
A note for later: in other languages and in older TypeScript code, you may see such lists written as an enum, for example enum Direction { North, South }. Unlike other types, an enum doesn't just disappear when the types are removed: it becomes JavaScript code of its own. That's why this site's settings don't allow it:
main.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.Newer TypeScript code often uses literal unions instead, and so will you.
Instructions
The program prints 90 three times, but a warrior should start with 120 health and a mage with 70. Above the function, create the type HeroClass for the three classes "warrior", "mage" and "ranger", and make it the type of the parameter heroClass. TypeScript then finds two typos: fix them, so that the program prints:
120
70
90Below the calls, create a type Hero for an object with a name (a text) and a heroClass of the type HeroClass. Then create kira of the type Hero: her name is "Kira", and she's a "ranger".
Print a line about Kira, using her properties and startingHealth. The program then prints:
120
70
90
Kira the ranger starts with 90 healthStart programming for free
3/10