Generic Union Types and Intersection Types: Deep Dive into Flexible Use of Type Combinations

Union types allow a variable to hold multiple possible types, connected by the | symbol. For example:

type Color = 'red' | 'blue' | 'green';
let color: Color = 'red';

This type combination is particularly common in generics and enables flexible definition of variadic arguments or polymorphic types. For instance:

function printItems(items: string | number | null) {
  if (items) {
    console.log(items);
  }
}

Syntax and Characteristics of Intersection Types

Intersection types are represented by the & symbol to indicate that multiple types share attributes or methods. For example:

type User = {
  name: string;
  age: number;
};
type Admin = {
  role: 'user' | 'admin';
};
type UserAdmin = User & Admin;

This type combination is highly useful when implementing polymorphic interfaces or inheritance, ensuring that an object satisfies multiple type constraints simultaneously.

Practical Scenarios for Flexible Use of Type Combinations

  1. Parameter Constraints in Generic Functions
    Limit the scope of function parameters using union types:
    function processItems(items: string[] | number[]) {
      items.forEach(item => {
        // General processing logic
      });
    }
    
  2. Type Assertion and Type Narrowing
    Utilize union types for type assertions while combining them with type narrowing to avoid runtime errors:
    const arr: (string | number)[] = [1, 'two'];
    const first = arr[0]; // Type-safe access
    if (typeof first === 'number') {
      console.log('number');
    }
    
  3. Extension of Conditional Types
    Combine conditional types to implement dynamic type constraints:
    type IsString<T> = T extends string ? true : false;
    const value: IsString<number> = 123; // Incorrect type assertion
    

Practical Tips for Type Combinations

  • Avoid Over-Generalization: Keep union types minimal to avoid introducing unnecessary type branches.
  • Leverage Type Aliases: Enhance code readability by using type aliases (e.g., type User = ...).
  • Combine with Type Guards: Use the as keyword for type assertions, combined with typeof or instanceof checks for type narrowing.
  • Test Coverage: Write unit tests for both union and intersection types to ensure all possible type combinations are covered.

Conclusion

The flexible use of generic union and intersection types is a core mechanism in TypeScript for achieving strong typing. By designing type combinations reasonably, you can significantly improve code maintainability, scalability, and type safety. In actual development, it is necessary to balance type complexity with performance based on business requirements while prioritizing code readability and testability.