Advanced Types
50 minTypeScript's advanced type system goes far beyond basic types, providing powerful mechanisms for creating flexible and precise type definitions. Union types, intersection types, literal types, and mapped types enable you to model complex real-world scenarios with type safety. These advanced types are essential for building robust applications that catch errors at compile time rather than runtime.
Union types, represented with the pipe operator (`|`), allow a variable to be one of several types. For example, `string | number` means a value can be either a string or a number. This is particularly useful for functions that can accept multiple input types or return different types based on conditions. TypeScript's type narrowing helps you work safely with union types by narrowing the type within conditional blocks.
Intersection types, represented with the ampersand operator (`&`), combine multiple types into one. An intersection type `A & B` means an object must satisfy both type A and type B simultaneously. This is useful for composing types and creating more specific types from general ones. For instance, you can combine a base user type with role-specific properties to create admin or regular user types.
Literal types allow you to specify exact values rather than general types. A literal type like `"red" | "blue"` means the value must be exactly the string "red" or "blue", not just any string. This enables type-safe enums, configuration objects, and discriminated unions. Discriminated unions combine literal types with other types to create powerful type-safe patterns for handling different states or variants.
Mapped types enable you to create new types by transforming properties of existing types. They use the `in` keyword to iterate over keys of a type and apply transformations. For example, you can create a `Readonly<T>` type that makes all properties readonly, or a `Partial<T>` type that makes all properties optional. Mapped types are the foundation of many TypeScript utility types.
Understanding these advanced types is crucial for writing maintainable TypeScript code. They enable you to express complex relationships between types, create reusable type utilities, and build type-safe APIs that catch errors before they reach production. Mastering advanced types separates intermediate TypeScript developers from experts.
Key Concepts
- Union types (|) allow values to be one of several types.
- Intersection types (&) combine multiple types into one.
- Literal types specify exact values, not just general types.
- Discriminated unions combine literal types with other types for type-safe patterns.
- Mapped types transform properties of existing types.
Learning Objectives
Master
- Creating and using union types for flexible type definitions
- Combining types with intersection types
- Using literal types for type-safe constants and enums
- Building discriminated unions for handling different states
Develop
- Type system thinking and advanced type design
- Understanding type relationships and transformations
- Creating reusable type utilities and patterns
Tips
- Use union types when a value can be one of several types.
- Use intersection types to combine and compose types.
- Prefer literal types over enums for simple constant sets.
- Use discriminated unions for type-safe state machines and variant handling.
Common Pitfalls
- Creating overly complex union types that are hard to work with.
- Using intersection types when union types would be more appropriate.
- Not using type guards with union types, leading to type errors.
- Forgetting that literal types are more specific than general types.
Summary
- Advanced types enable powerful and flexible type definitions.
- Union types allow values to be one of several types.
- Intersection types combine multiple types into one.
- Literal types and discriminated unions enable type-safe patterns.
- Mapped types transform existing types into new ones.
Exercise
Use union types and type guards to handle different data types.
type StringOrNumber = string | number;
function processValue(value: StringOrNumber): string {
if (typeof value === "string") {
return value.toUpperCase();
} else {
return value.toString();
}
}
console.log(processValue("hello")); // "HELLO"
console.log(processValue(42)); // "42"
Exercise Tips
- Try intersection types: type A = { a: number } & { b: string };
- Use discriminated unions for better type narrowing: type Result = { kind: 'success', data: T } | { kind: 'error', message: string };
- Create literal types: type Status = 'pending' | 'completed' | 'failed';
- Experiment with mapped types: type Readonly<T> = { readonly [P in keyof T]: T[P] };