Advanced TypeScript Patterns

January 18, 2024
typescript
advancedtypescript

Master advanced TypeScript concepts and patterns

Advanced TypeScript Patterns

Take your TypeScript skills to the next level with advanced patterns and techniques.

Generic Constraints and Utility Types

Conditional Types

typescript
// Basic conditional type
type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>;  // true
type Test2 = IsString<number>;  // false

// More complex conditional type
type NonNullable<T> = T extends null | undefined ? never : T;

type Example1 = NonNullable<string | null>;     // string
type Example2 = NonNullable<number | undefined>; // number

Mapped Types

typescript
// Make all properties optional
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// Make all properties required
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// Pick specific properties
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Omit specific properties
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// Custom mapped type
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

Advanced Generic Patterns

Generic Constraints

typescript
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// Usage
loggingIdentity("hello");     // OK
loggingIdentity([1, 2, 3]);  // OK
loggingIdentity(123);        // Error: number doesn't have length

Generic Function Overloads

typescript
function createElement<T extends string>(
  tagName: T
): HTMLElementTagNameMap[T];
function createElement<T extends keyof HTMLElementTagNameMap>(
  tagName: T
): HTMLElementTagNameMap[T];
function createElement(tagName: string): HTMLElement {
  return document.createElement(tagName);
}

// Usage with proper typing
const div = createElement('div');    // HTMLDivElement
const input = createElement('input'); // HTMLInputElement

Template Literal Types

typescript
// Basic template literals
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type HoverEvent = EventName<'hover'>; // 'onHover'

// More complex example
type ApiEndpoint<T extends string> = `/api/${T}`;
type UserEndpoint = ApiEndpoint<'users'>; // '/api/users'

// CSS property names
type CSSProperty<T extends string> = `--${T}`;
type CustomProperty = CSSProperty<'primary-color'>; // '--primary-color'

Advanced Utility Types

Recursive Types

typescript
type JSONValue = 
  | string 
  | number 
  | boolean 
  | null 
  | JSONValue[] 
  | { [key: string]: JSONValue };

// Deep readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

Branded Types

typescript
// Create branded types for type safety
type UserId = string & { __brand: 'UserId' };
type ProductId = string & { __brand: 'ProductId' };

function createUserId(id: string): UserId {
  return id as UserId;
}

function createProductId(id: string): ProductId {
  return id as ProductId;
}

// Usage - prevents mixing up IDs
function getUser(id: UserId) {
  // Implementation
}

const userId = createUserId('123');
const productId = createProductId('456');

getUser(userId);     // OK
getUser(productId);  // Error: ProductId is not assignable to UserId

Advanced Function Types

Function Overloads with Generics

typescript
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// Overload signatures
function apiCall<T extends string>(endpoint: T): Promise<ApiResponse<any>>;
function apiCall<T extends string, R>(
  endpoint: T, 
  data: R
): Promise<ApiResponse<R>>;

// Implementation
function apiCall<T extends string, R>(
  endpoint: T, 
  data?: R
): Promise<ApiResponse<any>> {
  // Implementation
  return Promise.resolve({ data: data || {}, status: 200, message: 'OK' });
}

Higher-Order Functions

typescript
// Function that returns a function with proper typing
function createValidator<T>(
  schema: Record<keyof T, (value: any) => boolean>
) {
  return (data: Partial<T>): data is T => {
    return Object.keys(schema).every(key => 
      schema[key as keyof T](data[key as keyof T])
    );
  };
}

// Usage
const userSchema = {
  name: (value: any) => typeof value === 'string',
  age: (value: any) => typeof value === 'number' && value > 0,
  email: (value: any) => typeof value === 'string' && value.includes('@')
};

const validateUser = createValidator(userSchema);

Module Augmentation

typescript
// Extend existing module types
declare global {
  interface Window {
    myCustomProperty: string;
  }
}

// Augment existing modules
declare module 'express' {
  interface Request {
    user?: {
      id: string;
      email: string;
    };
  }
}

Advanced Error Handling

typescript
// Result type for error handling
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E };

// Async result type
type AsyncResult<T, E = Error> = Promise<Result<T, E>>;

// Usage
async function fetchUser(id: string): AsyncResult<User> {
  try {
    const user = await api.getUser(id);
    return { success: true, data: user };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

Performance Considerations

Type-only imports

typescript
// Import only types (removed at runtime)
import type { User } from './types';
import type { ComponentProps } from 'react';

// Re-export types
export type { User, ApiResponse } from './types';

Conditional compilation

typescript
// Use const assertions for better inference
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
} as const;

// Type is inferred as literal types, not string | number

Best Practices

  1. Use strict mode - Enable all strict flags in tsconfig.json
  2. Prefer interfaces for object shapes - Use types for unions and computed types
  3. Use const assertions - For better type inference
  4. Avoid any - Use unknown or proper typing instead
  5. Use utility types - Leverage built-in utility types
  6. Document complex types - Add JSDoc comments for complex type definitions

Practice Projects

  1. Build a type-safe API client with generics
  2. Create a form validation library with conditional types
  3. Implement a state management system with branded types
  4. Design a plugin system with module augmentation