Advanced TypeScript Features

(Updated on )

Advanced TypeScript Features

TypeScript offers powerful features for creating robust and maintainable applications. In this guide, we’ll explore advanced TypeScript concepts and patterns.

Advanced Types

Union and Intersection Types

type StringOrNumber = string | number;
type NumberAndString = { num: number } & { str: string };

const value: StringOrNumber = "hello";
const obj: NumberAndString = { num: 42, str: "hello" };

Mapped Types

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

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface User {
  name: string;
  age: number;
}

type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

Conditional Types

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

type T0 = TypeName<string>;  // "string"
type T1 = TypeName<number>;  // "number"

Utility Types

Pick and Omit

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;
type TodoInfo = Omit<Todo, "completed">;

Record

type CatInfo = {
  age: number;
  breed: string;
};

type CatName = "miffy" | "boris";

const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" }
};

Decorators

function log(target: any, key: string) {
  let value = target[key];

  const getter = function() {
    console.log(`Getting ${key}`);
    return value;
  };

  const setter = function(newVal: any) {
    console.log(`Setting ${key} to ${newVal}`);
    value = newVal;
  };

  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Example {
  @log
  name: string;
}

Advanced Generics

Generic Constraints

interface Lengthwise {
  length: number;
}

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

Generic Classes

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

Type Guards

function isString(value: any): value is string {
  return typeof value === "string";
}

function processValue(value: string | number) {
  if (isString(value)) {
    // value is string
    console.log(value.toUpperCase());
  } else {
    // value is number
    console.log(value.toFixed(2));
  }
}

Best Practices

  1. Use type guards for runtime type checking
  2. Leverage utility types for common transformations
  3. Use decorators for cross-cutting concerns
  4. Implement proper error handling with custom types
  5. Use advanced generics for type-safe APIs

Next Steps

Happy coding!