Union types in TypeScript allow for increased flexibility by enabling a variable to hold more than one type of value.
Instead of restricting a variable to a single type (e.g., string, number, or boolean), a union type lets you define a combination of types.
This tutorial will walk you through the concept of union types in TypeScript, along with practical examples to illustrate their usage.
1. What Are Union Types?
In TypeScript, a union type allows a variable to hold values from two or more distinct types. Union types are declared by using the vertical bar | between the types.
Syntax:
let variable: type1 | type2 | type3;
Where type1, type2, and type3 are valid TypeScript types.
Example:
let value: string | number; value = "Hello"; // Valid value = 42; // Valid value = true; // Error: Type 'boolean' is not assignable to type 'string | number'
In this example, value can either be a string or a number, but it cannot be a boolean.
2. Simple Examples of Union Types
Union types provide a convenient way to handle values that can have different types without losing type safety.
Example 1: Basic Union Types
let id: number | string; id = 101; // Valid id = "ABC123"; // Valid id = true; // Error: Type 'boolean' is not assignable to type 'string | number'
Example 2: Function Parameters with Union Types
function printId(id: number | string): void { console.log("Your ID is: " + id); } printId(123); // Valid printId("ABC123"); // Valid printId(true); // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'
3. Type Narrowing
TypeScript allows us to narrow down the type within a union using conditional logic like typeof or instanceof. This ensures the correct type is used within a block of code.
Example 3: Type Narrowing Using typeof
function printDetails(value: string | number): void { if (typeof value === "string") { console.log("String value: " + value.toUpperCase()); } else { console.log("Number value: " + (value * 2)); } } printDetails("Hello"); // Output: String value: HELLO printDetails(21); // Output: Number value: 42
In this example, TypeScript checks the type of value at runtime and narrows the union type to either string or number inside the respective if and else blocks.
4. Union Types with Functions
Union types are especially useful in function parameters and return types, enabling you to create more flexible functions.
Example 4: Function with Union Return Types
function calculate(value: number | string): number | string { if (typeof value === "number") { return value * 10; } else { return `Value is: ${value}`; } } console.log(calculate(5)); // Output: 50 console.log(calculate("Test")); // Output: Value is: Test
This function can return either a number or a string based on the input type.
5. Union Types in Arrays
TypeScript allows you to declare arrays that can hold multiple types of values using union types.
Example 5: Arrays with Union Types
let mixedArray: (number | string)[] = [1, "Hello", 2, "World"]; mixedArray.push(3); // Valid mixedArray.push("Test"); // Valid mixedArray.push(true); // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'
In this example, mixedArray can contain both number and string values but cannot hold any other types.
6. Union Types with Objects
You can also use union types to specify objects with different structures. This is particularly useful when you need to handle different object types in a flexible manner.
Example 6: Union Types with Objects
type Admin = { role: string; accessLevel: number; }; type User = { username: string; isLoggedIn: boolean; }; function printUserInfo(person: Admin | User): void { if ('role' in person) { console.log(`Admin Role: ${person.role}, Access Level: ${person.accessLevel}`); } else { console.log(`User: ${person.username}, Logged In: ${person.isLoggedIn}`); } } const admin: Admin = { role: "SuperAdmin", accessLevel: 5 }; const user: User = { username: "JohnDoe", isLoggedIn: true }; printUserInfo(admin); // Output: Admin Role: SuperAdmin, Access Level: 5 printUserInfo(user); // Output: User: JohnDoe, Logged In: true
Here, the function handles both Admin and User object types by using the in operator to check which properties are available and then performs the relevant actions.
7. Common Use Cases for Union Types
Union types are useful in various scenarios where flexibility is required. Some common use cases include:
Example 7: Handling API Responses
APIs often return different types of data based on different conditions (e.g., success or error messages).
type SuccessResponse = { status: "success"; data: string; }; type ErrorResponse = { status: "error"; message: string; }; function handleApiResponse(response: SuccessResponse | ErrorResponse): void { if (response.status === "success") { console.log("Data: " + response.data); } else { console.log("Error: " + response.message); } } const successResponse: SuccessResponse = { status: "success", data: "Data retrieved successfully" }; const errorResponse: ErrorResponse = { status: "error", message: "Failed to retrieve data" }; handleApiResponse(successResponse); // Output: Data: Data retrieved successfully handleApiResponse(errorResponse); // Output: Error: Failed to retrieve data
In this example, the function gracefully handles both success and error responses using union types.
Conclusion
Union types in TypeScript are a valuable tool for increasing flexibility while maintaining type safety. They allow a variable, parameter, or return type to accept multiple types, making your code more versatile.
By using type narrowing, you can ensure that the correct type is used in any given situation. From handling API responses to working with arrays and objects, union types are an essential feature in TypeScript for writing robust, type-safe applications.