Mastering TypeScript Generics: Building Scalable and Reusable Components

Learn how to use TypeScript generics to create flexible, scalable, and reusable components that improve your code quality and maintainability.

TypeScript generics are a powerful feature that allow you to create components and functions which can work with a variety of data types instead of being limited to one. This flexibility helps you write reusable code components and improve scalability in your projects. In this tutorial, we'll explore the basics of generics and build practical examples to master this concept.

Generics work by defining a placeholder type parameter, usually with a letter like . This placeholder can be replaced with any type when the function or class is used. Let's start with a simple example of a generic function.

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

const output1 = identity<string>("Hello Generics!"); // Output is string
const output2 = identity<number>(42); // Output is number

console.log(output1); // Hello Generics!
console.log(output2); // 42

In the example above, function identity takes an argument of type T and returns the same type. When calling the function, you specify the type, and TypeScript enforces that the argument and return types match.

Next, let's create a generic interface for an array wrapper that ensures type safety but works with many types.

typescript
interface MyArray<T> {
  items: T[];
  add(item: T): void;
  get(index: number): T;
}

class ArrayWrapper<T> implements MyArray<T> {
  items: T[] = [];

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

  get(index: number): T {
    return this.items[index];
  }
}

const numberArray = new ArrayWrapper<number>();
numberArray.add(10);
numberArray.add(20);
console.log(numberArray.get(1)); // 20

const stringArray = new ArrayWrapper<string>();
stringArray.add("hello");
stringArray.add("world");
console.log(stringArray.get(0)); // hello

Here, the ArrayWrapper class uses generics to handle arrays of any type, but retains strict type checking. This means you can't accidentally add a string to a number array, reducing bugs.

Generics also shine when building reusable React components or utility functions. For instance, a generic function to merge two objects can be written as follows:

typescript
function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = mergeObjects(
  { name: "Alice" },
  { age: 30 }
);

console.log(merged.name); // Alice
console.log(merged.age);  // 30

In this example, two objects of different types are merged and the resulting object has a combined type of both. Using extends object ensures we only accept objects and the return type reflects the merged type, enhancing type safety.

To summarize, here are key points to master TypeScript generics: - Use generics to write flexible functions, interfaces, and classes. - Define generic type parameters inside angle brackets: . - Use constraints like extends to restrict acceptable types. - Generics help catch errors early and improve code reuse and scalability. Try incorporating generics in your next TypeScript project to experience clearer and more maintainable code!