UX/UI

    Mastering TypeScript: Advanced Patterns

    Feb 20, 2024
    5 min read
    Brandon S. Dement

    Mastering TypeScript: Advanced Patterns

    TypeScript has gained immense popularity among developers for its powerful type system that helps catch errors early and enhances code quality. While basic types and interfaces are useful, mastering advanced TypeScript patterns can take your code to the next level.

    Discriminated Unions

    Discriminated unions are a pattern where you use a property to discriminate between different types in a union. This is particularly useful for handling different variants of a data structure.

    type Success = { 
      status: 'success'; 
      data: User[];
    };
    
    type Error = { 
      status: 'error'; 
      error: string;
    };
    
    type Response = Success | Error;
    
    function handleResponse(response: Response) {
      if (response.status === 'success') {
        // TypeScript knows that response is Success
        return response.data;
      } else {
        // TypeScript knows that response is Error
        throw new Error(response.error);
      }
    }
    

    Conditional Types

    Conditional types allow you to create types that depend on conditions. They’re similar to ternary operators but for types.

    type IsString<T> = T extends string ? true : false;
    
    // Usage
    type A = IsString<string>; // true
    type B = IsString<number>; // false
    

    Mapped Types

    Mapped types allow you to create new types based on existing ones by transforming properties. They’re particularly useful for creating utility types.

    type Optional<T> = {
      [P in keyof T]?: T[P];
    };
    
    // Usage
    type User = {
      id: number;
      name: string;
      email: string;
    };
    
    type PartialUser = Optional<User>;
    // Equivalent to { id?: number; name?: string; email?: string; }
    

    Utility Types

    TypeScript provides several built-in utility types that solve common type manipulation needs, such as Partial, Required, Pick, Omit, Exclude, Extract, and many more.

    type User = {
      id: number;
      name: string;
      email: string;
      password: string;
    };
    
    type PublicUser = Omit<User, 'password'>;
    // Equivalent to { id: number; name: string; email: string; }
    

    Generic Constraints

    Generic constraints allow you to limit the types that can be used with a generic type parameter. This is useful when you need to ensure certain properties exist on the types you’re working with.

    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
    
    // Usage
    const user = { id: 1, name: 'Brandon Dement' };
    const id = getProperty(user, 'id'); // Works
    const age = getProperty(user, 'age'); // Error: 'age' is not assignable to parameter of type 'id' | 'name'
    

    Conclusion

    Mastering these advanced TypeScript patterns will help you write more robust, maintainable code. They provide the tools to express complex relationships between types and catch errors early, leading to a better development experience and higher-quality software.

    Tags:
    TypeScript
    JavaScript
    Patterns