Narrowing with typeof
A function that gets a string | number usually needs to treat each type differently. In JavaScript, you find out the type with typeof, which you know from the JavaScript course: typeof 50 is "number", and typeof "Silver Sword" is "string". TypeScript understands such a check. In this example, the guild pays out quest rewards, and it keeps 5 gold of every gold reward for itself:
function payout(reward: string | number): string {
if (typeof reward === "string") {
return `You get the ${reward.toUpperCase()}`;
} else {
return `You get ${reward - 5} gold (5 for the guild)`;
}
}
console.log(payout("Silver Sword"));
console.log(payout(50));This prints:
You get the SILVER SWORD
You get 45 gold (5 for the guild)Inside the if, TypeScript knows that reward is a string, so toUpperCase() is allowed. In the else branch, only number is left, so subtraction is allowed. This is called narrowing: a check makes the type narrower inside a branch, from string | number to just string, or just number.
Without the check, TypeScript doesn't allow the subtraction:
function payout(reward: string | number): string {
return `You get ${reward - 5} gold (5 for the guild)`;
}main.ts(2,23): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.You know this message from the Basic Types lesson: the value on the left of - must be a number.
Narrowing also works after return. When the if branch ends with return, the rest of the function runs only for the other type, and TypeScript knows that:
function payout(reward: string | number): string {
if (typeof reward === "string") {
return `You get the ${reward.toUpperCase()}`;
}
return `You get ${reward - 5} gold (5 for the guild)`;
}Below the if, reward is a number. Programmers often write functions this way: first the special case, which returns, then the main case without extra braces.
Arrays can hold unions too. TypeScript infers the type of [12, "Old Key", 40] as (string | number)[]: an array whose elements are texts or numbers. When you write such a type yourself, don't forget the parentheses. Without them, number | string[] means “a number, or an array of texts”, and TypeScript refuses the numbers in the array:
const loot: number | string[] = [12, "Old Key", 40];main.ts(1,34): error TS2322: Type 'number' is not assignable to type 'string'.
main.ts(1,49): error TS2322: Type 'number' is not assignable to type 'string'.Typical mistakes:
typeofgives the name of the type in lowercase:"string","number","boolean". Fortypeof reward === "String", TypeScript reportserror TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"String"' have no overlap.The long list in the message shows every text thattypeofcan give, and"String"isn't one of them.- Narrowing applies only inside the branch, or after an
ifthat returns. Below anifthat doesn't return, the type isstring | numberagain.
Instructions
Run the program: TypeScript doesn't allow price * count, because price may be a text. At the beginning of formatPrice, add a typeof check: when price is a text, the function returns it in capital letters. The program then prints:
50 gold
FREEBelow the calls, create an array prices of the type (number | string)[] with the values 12, "sold out" and 40.
Go through prices with for...of, and print formatPrice(price, 2) for each price. The program then prints:
50 gold
FREE
24 gold
SOLD OUT
80 goldStart programming for free
2/10