TypeScript Advanced Types Explained with Examples
TypeScript offers several advanced types that go beyond basic types, allowing for more flexible and powerful type systems. These advanced types help in creating robust applications by providing additional ways to define and enforce type constraints. This article explores some of these advanced types with examples.
Union Types
Union types allow a variable to be one of several types. This can be useful when a value could be of multiple types but should be handled appropriately based on its actual type.
// Union type example
function formatValue(value: string | number): string {
if (typeof value === 'string') {
return `String: ${value}`;
} else {
return `Number: ${value.toFixed(2)}`;
}
}
console.log(formatValue("Hello"));
console.log(formatValue(123.456));
In this example, the `formatValue` function accepts either a string or a number and formats the value accordingly.
Intersection Types
Intersection types combine multiple types into one. An object of an intersection type will have all the properties of the combined types. This is useful for composing multiple types together.
// Intersection type example
interface Person {
name: string;
age: number;
}
interface Contact {
email: string;
phone: string;
}
type Employee = Person & Contact;
const employee: Employee = {
name: "John Doe",
age: 30,
email: "john.doe@example.com",
phone: "123-456-7890"
};
console.log(employee);
Here, the `Employee` type is an intersection of `Person` and `Contact`, meaning it contains properties from both interfaces.
Literal Types
Literal types specify exact values a variable can hold. This can be particularly useful for ensuring that only certain specific values are allowed.
// Literal type example
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
console.log(`Moving ${direction}`);
}
move("up"); // Valid
move("down"); // Valid
// move("side"); // Error: Argument of type '"side"' is not assignable to parameter of type 'Direction'.
The `Direction` type here is restricted to four specific string values, ensuring that only these directions can be used in the `move` function.
Tuple Types
Tuple types represent an array with a fixed number of elements where each element can have a different type. Tuples are useful for representing fixed-size collections of heterogeneous items.
// Tuple type example
let user: [string, number] = ["Alice", 30];
console.log(user[0]); // "Alice"
console.log(user[1]); // 30
// user = [30, "Alice"]; // Error: Type 'number' is not assignable to type 'string'.
The `user` tuple is defined with a string followed by a number, and this structure must be maintained.
Conditional Types
Conditional types allow types to be determined based on conditions. They provide a way to select one type or another based on a condition.
// Conditional type example
type IsString = T extends string ? "Yes" : "No";
type Test1 = IsString; // "Yes"
type Test2 = IsString; // "No"
In this example, the `IsString` type checks if a type `T` is a string. It returns `"Yes"` if it is and `"No"` otherwise.
Mapped Types
Mapped types allow for creating new types by transforming properties of an existing type. This is useful for modifying or extending existing types.
// Mapped type example
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
const readonlyPerson: ReadonlyPerson = {
name: "Alice",
age: 30
};
// readonlyPerson.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.
The `ReadonlyPerson` type transforms the `Person` type by making all its properties read-only.
Conclusion
TypeScript’s advanced types provide powerful tools for defining and managing complex type requirements. By utilizing union, intersection, literal, tuple, conditional, and mapped types, developers can create more robust and maintainable applications. Understanding and applying these types effectively can significantly enhance the type safety and flexibility of TypeScript code.