Union types in TypeScript allow you to work with variables that can hold multiple types, providing both flexibility and type safety. Whether you’re working with dynamic data from APIs or defining complex structures, union types can help make your code more predictable and error-free. This tutorial will cover everything you need to know about union types, from basic usage to real-world applications.
Union types are one of TypeScript’s most powerful features, allowing you to combine multiple types in a single variable. They’re perfect for cases where a variable might hold different kinds of values, such as a string or number. Union types make your code more flexible and robust while still maintaining type safety, helping you handle real-world data effectively.
A union type in TypeScript is a type that allows a variable to hold values of more than one type. You define union types using the | symbol, specifying each type that the variable can hold.
let value: string | number;
value = "Hello"; // OK
value = 42; // OK
// value = true; // Error: Type 'boolean' is not assignable to type 'string | number'
In this example:
value
can hold either a string
or number
, providing flexibility.boolean
), TypeScript will throw an error.Union types can be used for variables, parameters, and return types, allowing you to specify multiple acceptable types in various parts of your code.
The syntax for union types is straightforward. Use the |
symbol to separate the types that a variable can hold.
let id: string | number;
id = "user123";
id = 1001;
Here, id
can hold either a string
or number
, making it suitable for scenarios where an identifier might be alphanumeric or numeric.
You can also use union types in function parameters and return types, making your functions more adaptable.
function printValue(value: string | number): void {
console.log(`Value: ${value}`);
}
printValue("Hello"); // Output: Value: Hello
printValue(123); // Output: Value: 123
In this example:
printValue
accepts either a string
or number
as the parameter, providing flexibility without sacrificing type safety.Union types are especially useful in functions, allowing you to handle multiple data types without writing separate functions for each type.
Suppose you have a function that needs to process both strings and numbers differently. You can use a union type to define the parameter, then use conditional checks to handle each type appropriately.
function processInput(input: string | number): void {
if (typeof input === "string") {
console.log(`String input: ${input.toUpperCase()}`);
} else {
console.log(`Number input: ${input.toFixed(2)}`);
}
}
processInput("hello"); // Output: String input: HELLO
processInput(3.1415); // Output: Number input: 3.14
In this example:
processInput
accepts either a string
or number
, handling each type differently based on the type check.Type narrowing is a way to refine union types within a function to allow type-specific operations. TypeScript uses conditional statements and type guards like typeof and instanceof to determine the type at runtime.
typeof
The typeof
operator is commonly used with union types to check the type of a variable.
function describeValue(value: string | number | boolean): string {
if (typeof value === "string") {
return `String: ${value}`;
} else if (typeof value === "number") {
return `Number: ${value}`;
} else {
return `Boolean: ${value ? "true" : "false"}`;
}
}
console.log(describeValue("TypeScript")); // Output: String: TypeScript
console.log(describeValue(42)); // Output: Number: 42
console.log(describeValue(true)); // Output: Boolean: true
In this example:
describeValue
uses typeof
to determine the specific type of value
and then processes it accordingly.instanceof
When working with classes and objects, you can use instanceof
to narrow down the type in union types.
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
makeSound(new Dog()); // Output: Woof!
makeSound(new Cat()); // Output: Meow!
Here:
makeSound
accepts either a Dog
or Cat
and uses instanceof
to check the specific type before calling the appropriate method.API responses often have different types based on the outcome, such as success or error. Union types allow you to handle these cases flexibly.
type SuccessResponse = {
status: "success";
data: string;
};
type ErrorResponse = {
status: "error";
error: string;
};
type ApiResponse = SuccessResponse | ErrorResponse;
function handleApiResponse(response: ApiResponse): void {
if (response.status === "success") {
console.log("Data:", response.data);
} else {
console.error("Error:", response.error);
}
}
handleApiResponse({ status: "success", data: "User data" });
handleApiResponse({ status: "error", error: "Something went wrong" });
In this example:
ApiResponse
is a union of SuccessResponse
and ErrorResponse
.handleApiResponse
checks status to determine which structure it’s handling.Union types are useful for functions that handle different input formats, such as specifying configuration options.
type Config = { path: string } | { url: string };
function loadResource(config: Config): void {
if ("path" in config) {
console.log(`Loading resource from path: ${config.path}`);
} else {
console.log(`Loading resource from URL: ${config.url}`);
}
}
loadResource({ path: "/local/file.txt" }); // Output: Loading resource from path: /local/file.txt
loadResource({ url: "https://example.com" }); // Output: Loading resource from URL: https://example.com
In this example:
Config
type is a union of objects with either a path or a url property.loadResource
function checks for the presence of each property to determine which logic to execute.Union types and literal types are related but serve different purposes.
string | number
."success" | "error"
.type Status = "success" | "error" | "loading";
function updateStatus(status: Status): void {
console.log(`Status updated to: ${status}`);
}
updateStatus("success"); // OK
updateStatus("loading"); // OK
// updateStatus("failed"); // Error: Argument of type '"failed"' is not assignable to parameter of type 'Status'
In this example:
Status
is a union of literal types, restricting status
to specific values.typeof
, in
, and instanceof
to narrow down union types, enabling specific operations based on the type.Union types in TypeScript allow you to work with variables that can hold multiple types, enhancing flexibility without sacrificing type safety. Through type narrowing, you can create functions and structures that handle dynamic data accurately. By understanding and implementing union types effectively, you can make your TypeScript applications more adaptable and resilient in handling various data types and conditions.