Learn TypeScript Generics with Examples

post-title

Hey there, fellow coders! Ever wished your code could be more adaptable and reusable without turning into a puzzle? Well, say hello to TypeScript Generics! In this blog post, we're going to take a stroll through the basics of TypeScript Generics - a fancy term for making your code super flexible. No jargon, no headaches, just a straightforward guide to help you write code that can handle different data types like a champ. Let's dive in and make your coding life a bit simpler, shall we?

What are Generics?

Generics are a tool that allows you to write flexible, reusable code while maintaining type safety. They are a way of creating reusable components that can work over a variety of types rather than a single one. Imagine you're writing a function to return the last item in an array. Without generics, you might write something like this:

function getLastItem(arr: any[]) {
    return arr[arr.length - 1];
}

This works, but we lose information about the type of the returned item. With generics, we can write:

function getLastItem<T>(arr: T[]): T {
    return arr[arr.length - 1];
}

Here, T is a type variable—a stand-in for any type. This version of the function has the same functionality, but now it also preserves the type information.

Example Use Cases

1. Creating a Linked List

Generics are often used in data structures. For example, here's how you might define a simple linked list node in TypeScript:

class ListNode<T> {
    constructor(public value: T, public next: ListNode<T> | null = null) {}
}

2. Promise Return Types

When working with Promises, TypeScript's built-in Promise<T> type makes it easy to know what type of data a promise will resolve with:

function getEmployee(id: string): Promise<Employee> {
    // Implementation here...
}

In this example, Promise<Employee> is a generic type that indicates the promise will resolve with an instance of the Employee class.

3. Creating a Reusable Box Class

Generics are not just for functions; you can use them in classes too. Here's a simple Box class that can hold any type of value:


class Box<T> {
  private value: T;

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

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

const stringBox = new Box('TypeScript');
const numberBox = new Box(42);

console.log(stringBox.getValue()); // 'TypeScript'
console.log(numberBox.getValue()); // 42

4. Working with Generic Constraints

You can also set constraints on the types that can be used with generics. In this example, we ensure that the input type must have a length property:


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

logLength('Hello, TypeScript!'); // 18
logLength([1, 2, 3, 4, 5]);      // 5

5. Using Type Parameters in Generic Constraints

You can declare a type parameter that is constrained by another type parameter. For example, here we’d like to get a property from an object given its name. We’d like to ensure that we’re not accidentally grabbing a property that does not exist on the obj, so we’ll place a constraint between the two types:

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}
 
let x = { a: 1, b: 2, c: 3, d: 4 };
 
getProperty(x, "a");
getProperty(x, "m");
Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.

And there you have it! We've taken a stroll through the world of TypeScript Generics, unlocking the magic of flexibility and reusability in our code. With Generics, coding becomes a breeze. Whether you're creating functions that work seamlessly with different data types or crafting versatile classes, TypeScript Generics empowers you to write code that's not just functional but adaptable to whatever you throw at it. I encourage you to read more details about it on TypeScript official website here : https://www.typescriptlang.org/docs/handbook/2/generics.html