Unions and the Power of Literals
What if a value can be more than one type?
So far, we've dealt with values that are one specific type. But what if a variable could be either a string
OR a number
? This is a common scenario, and TypeScript handles it with union types.
The |
Operator
You create a union type by joining two or more types with the pipe |
character.
let myId: string | number;
Now, myId
can be assigned either a string or a number. When you have a union, you can only perform operations that are valid for every type in that union. To do type-specific operations, you first need to check what the type is. This is called narrowing.
Exercise 1: Narrowing with typeof
- The
processId
function takes anid
of typestring | number
. - It needs to return the uppercased string if it's a string, or the number formatted to two decimal places if it's a number.
- Use an
if
statement with atypeof
check to narrow the type and implement the logic.
Click to see the solution
function processId(id: string | number) {
if (typeof id === "string") {
// Inside this block, TypeScript knows 'id' is a string
return id.toUpperCase();
} else {
// Here, it knows 'id' is a number
return id.toFixed(2);
}
}
Combining Unions with Literal Types
The typeof
guard is great for primitives. But the true power of unions is unlocked when you combine them with literal types.
Remember in Lesson 1, we saw that a const
variable gets its value as its type?
const charName = "Bowser"; // Type is "Bowser"
We can use this concept to build unions of specific values. This allows us to define a finite set of allowed options for a variable, which is an incredibly powerful pattern.
type Status = 'pending' | 'success' | 'error';
A variable of type Status
can only ever be one of those three exact strings. This makes typos and invalid states impossible.
Exercise 2: The Status Checker
- Define a
type
alias namedLogLevel
that can only be'info'
,'warn'
, or'error'
. - The
log
function takes amessage
and alevel
of typeLogLevel
. - The last function call has a typo (
'errro'
). Because we're using a literal union, TypeScript catches this bug. Fix the typo. - Inside the function, use a
switch
statement to handle the different log levels.
Click to see the solution
type LogLevel = 'info' | 'warn' | 'error';
function log(message: string, level: LogLevel) {
switch (level) {
case 'info':
console.log(`INFO: ${message}`);
break;
case 'warn':
console.warn(`WARN: ${message}`);
break;
case 'error':
console.error(`ERROR: ${message}`);
break;
}
}
log("User logged in.", "info");
log("User password expired.", "warn");
log("DB connection failed.", "error");
By combining union and literal types, you create a powerful safety net. You are explicitly telling TypeScript the exact values that are valid, making your code more robust, easier to read, and eliminating an entire category of bugs.