TypeScript Tutorial
TypeScript Union Types
A union is one of several types: string | number. Narrow it before you use the value as one of them.
One value, several possible types
A union is written with a vertical bar: string | number means the value is a string or a number, not both at once. You do not know which until you check. The checker will not let you call.toUpperCase() on it until you have narrowed it to string.
In C++, a pointer might address an object or it might be nullptr. You test before you dereference. In TypeScript, string | null is that idea: a value, or nothing. You test before you use it as a string.
string or number
typeof is the usual narrow for primitives. After typeof value === "string", the block treats value as a string. The else branch treats it as a number if those are the only two members of the union.
Example
function show(value: string | number): void {
if (typeof value === "string") {
console.log("text: " + value.toUpperCase());
} else {
console.log("count: " + (value + 1));
}
}
show("ok");
show(7);Run this in /typescript/try. Delete the if and callvalue.toUpperCase() directly. tsc should stop you: a number has no such method.
string or null
null means no value. A function that looks up a user might return a name or null. Annotate that as string | null. Compare with !== null before you treat it as text.
Example
function findName(id: number): string | null {
if (id === 1) {
return "Ada";
}
return null;
}
const name: string | null = findName(1);
if (name !== null) {
console.log("hello, " + name);
} else {
console.log("no user");
}
const missing: string | null = findName(9);
if (missing === null) {
console.log("still none");
}That if (name !== null) is the TypeScript version of if (p != nullptr). Skip the check and the compiler refuses to concatenate name as if it were definitely a string.
undefined is a separate empty
JavaScript also has undefined: a variable with no value yet, or a missing field. A union can include it: string | undefined. Test with !== undefined, or handle both empties with a truthiness check when empty string is not a valid value.
Example
function label(title: string | undefined): string {
if (title === undefined) {
return "(untitled)";
}
return title;
}
let heading: string | undefined = undefined;
console.log(label(heading));
heading = "Chapter 1";
console.log(label(heading));Strict TypeScript does not let you assign null to a plain string. You must write the union. A later chapter on strict null covers that rule in more depth. The habit starts here: if it might be missing, say so in the type.
Narrow before you use
| Union | Typical check |
|---|---|
string | number | typeof value === "string" |
string | null | value !== null |
string | undefined | value !== undefined |
After the check, TypeScript narrows the type in that block. You did not convert the value. You proved which member of the union you have, the way a pointer check proves there is an object.
Unions versus enums
A union of string literals such as "red" | "green" is a closed set without an enum object. An enum is better when you want a named constant you can switch on as Color.Red. A union is better when the value really is one of several types, including null.
Do not dereference a union as if it were already one member. value.toUpperCase() onstring | number is a type error. Narrow first. The same rule as “do not use *puntil you know p is not null.”
Next: functions — a named block with a return type you can call again.