TypeScript Generics with Examples

TypeScript generics are a powerful feature that allow you to create reusable and type-safe components. Generics provide a way to create classes, functions, and interfaces that work with a variety of types while maintaining strong type safety. This article will introduce you to generics and demonstrate how to use them with practical examples.

Understanding Generics

Generics enable you to define a component with a placeholder for the type it operates on. Instead of specifying a concrete type, you use a generic type parameter that can be replaced with any type when the component is used.

Basic Syntax

The basic syntax for defining a generic type is to use angle brackets <> with a type parameter name. Here’s a simple example:

function identity(value: T): T {
  return value;
}

const stringIdentity = identity("Hello"); // string
const numberIdentity = identity(123); // number

In this example, identity is a generic function that takes a parameter value of type T and returns a value of the same type. The type parameter T is replaced with the actual type when the function is called.

Generics with Classes

Generics can also be used with classes to create flexible and reusable data structures. Here’s an example of a generic class:

class Box {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }
}

const stringBox = new Box("TypeScript");
console.log(stringBox.getValue()); // Output: TypeScript

const numberBox = new Box(42);
console.log(numberBox.getValue()); // Output: 42

In this example, the Box class is defined with a generic type parameter T. The class has a private property value of type T and a method getValue that returns the value of type T.

Generics with Interfaces

Generics can be used with interfaces to create flexible and type-safe interfaces. Here’s an example:

interface Pair<T, U> {
  first: T;
  second: U;
}

const pair: Pair<string, number> = {
  first: "Age",
  second: 30
};

console.log(pair.first); // Output: Age
console.log(pair.second); // Output: 30

In this example, the Pair interface is defined with two generic type parameters T and U. The interface represents a pair of values with types T and U, respectively.

Generics in Functions

Generics can be used in functions to handle multiple types while maintaining type safety. Here’s an example of a generic function that works with arrays:

function reverseArray(items: T[]): T[] {
  return items.reverse();
}

const reversedStringArray = reverseArray(["one", "two", "three"]);
console.log(reversedStringArray); // Output: ["three", "two", "one"]

const reversedNumberArray = reverseArray([1, 2, 3]);
console.log(reversedNumberArray); // Output: [3, 2, 1]

In this example, the reverseArray function takes an array of type T and returns a reversed array of the same type. The type parameter T ensures that the function works with arrays of any type while maintaining type safety.

Constraints on Generics

Sometimes you may need to impose constraints on the generic type parameter to ensure it has certain properties. This is done using constraints:

function logLength(item: T): void {
  console.log(item.length);
}

logLength("Hello, TypeScript"); // Output: 16
logLength([1, 2, 3]); // Output: 3
// logLength(123); // Error: number does not have a length property

In this example, the logLength function is constrained to types that have a length property. This allows the function to accept strings and arrays but not numbers or other types without a length property.

Conclusion

Generics in TypeScript provide a powerful way to create flexible and reusable components while maintaining strong type safety. By understanding and utilizing generics, you can write more generic and adaptable code, improving the overall quality and maintainability of your TypeScript applications.

Experiment with generics in your projects to see their benefits in action and enhance your TypeScript programming skills.