· TypeScript · Admin

Understanding TypeScript Generics

Understanding TypeScript Generics

Generics are one of the most powerful features of TypeScript, allowing you to create reusable components that work with multiple types while still maintaining type safety. However, they can also be one of the more confusing concepts for developers new to TypeScript.

What Are Generics?

Generics allow you to create "type variables" that can be used to create reusable components that work with multiple types, rather than a single type. This enables you to write code that is both flexible and type-safe.

A Simple Example

Without generics, you might write a function that returns whatever it is passed:

function identity(arg: any): any {
  return arg;
}

The problem with this is that you lose type information. If you pass in a string, you don't get back a typed string -- you get any.

With generics, you can preserve the type information:

function identity<T>(arg: T): T {
  return arg;
}

const result = identity("Hello, TypeScript");
// result is of type string

Common Use Cases

1. Generic Functions

Create functions that work with multiple types:

function reverse<T>(array: T[]): T[] {
  return [...array].reverse();
}

const numbers = reverse([1, 2, 3, 4, 5]); // number[]
const strings = reverse(["a", "b", "c"]); // string[]

2. Generic Interfaces

Create reusable interfaces:

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

interface User {
  id: number;
  name: string;
  email: string;
}

const userResponse: ApiResponse<User> = {
  data: { id: 1, name: "John Doe", email: "john@example.com" },
  status: 200,
  message: "Success",
};

3. Generic Classes

Create reusable classes:

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  size(): number {
    return this.items.length;
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.peek(); // type: number

Generic Constraints

Sometimes you want to limit the types that can be used with a generic. You can do this with constraints:

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length);
}

logLength("Hello"); // ✅ Works, string has length
logLength([1, 2, 3]); // ✅ Works, array has length
logLength({ length: 10 }); // ✅ Works, object has length property
logLength(42); // ❌ Error, number doesn't have length property

Advanced Concepts

Default Type Parameters

You can provide default types for generics:

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

// Now you can use ApiResponse without specifying the type
const response: ApiResponse = {
  data: "Hello",
  status: 200,
  message: "Success",
};

Conditional Types

Create types that depend on other types:

type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

Utility Types

TypeScript provides several built-in utility types that use generics:

  • Partial<T>: Make all properties of T optional
  • Readonly<T>: Make all properties of T readonly
  • Pick<T, K>: Pick specific properties K from T
  • Omit<T, K>: Omit specific properties K from T
  • Exclude<T, U>: Exclude types from T that are assignable to U
  • Extract<T, U>: Extract types from T that are assignable to U
  • ReturnType<T>: Get the return type of a function

Best Practices

  1. Use descriptive type parameter names: Prefer TUser over just T for clarity
  2. Don't overuse generics: If a function only works with one type, you probably don't need generics
  3. Provide defaults when possible: Makes generics easier to use
  4. Use constraints appropriately: They help with type safety and provide better autocompletion

Conclusion

Generics are a powerful tool in your TypeScript toolkit. They allow you to write reusable, type-safe code that is both flexible and maintainable. While they might seem intimidating at first, understanding how to use generics will greatly improve your TypeScript skills and make you a more effective developer.

Practice using generics in your code, and soon you'll find them indispensable!