DEV Community

Remon Fawzi
Remon Fawzi

Posted on

TypeScript generics simplified

What are generics in TypeScript?

Generics help us define functions or classes once, and expect different type declarations for them.

Let's start with an example
I want to create a function that takes an array and returns a random element of that array

function getRandomElement(arr:number[]) : number {
   const randomIndex = Math.floor(Math.random() * arr.length);
   return arr[randomIndex];
}
Enter fullscreen mode Exit fullscreen mode

The above function takes an array of numbers and returns one random element of these numbers.

If I want to use the same function but with an array of strings, Typescript will complain, and I'll need to create another function that accepts an array of strings and returns a string.

Generic types are introduced to solve such situations, let's write our function using a generic type

function getRandomElement<GenericType>(arr:GenericType[]): GenericType {
    const randomIndex = Math.floor(Math.random() * arr.length);
    return arr[randomIndex];
}
Enter fullscreen mode Exit fullscreen mode

Now our function takes a generic type which may be a string, number, or any other type, and returns an element of that type.

And this is how we would use our function

const randomElement1 = getRandomElement<number>([1, 2, 3])
const randomElement2 = getRandomElement<string>(['a', 'b', 'c'])
Enter fullscreen mode Exit fullscreen mode

So, we pass a generic type that tells the function what to expect as parameters, and what to return.

Can we pass multiple generic types?

Yes, you can pass multiple generic types, let's see an example.

const merge = <T1, T2>(obj1: T1, obj2: T2): T1 & T2 => {
  return { ...obj1, ...obj2 };
};
Enter fullscreen mode Exit fullscreen mode

And here's how we would use our function

merge<{ name: string }, { age: number }>({ name: "John" }, { age: 222 });
Enter fullscreen mode Exit fullscreen mode

Typescript can also infer the generic types from the passed parameters

merge({ name: "John", age: 22 }, { age: 2 }); // TS will infer the generic types from the passed parameters
Enter fullscreen mode Exit fullscreen mode

There's only one problem with our function, if anyone passes anything other than an object, it'll throw an error, cause we can only use spread operators here return { ...obj1, ...obj2 }; with objects.

We can solve this by setting constraints to our generic types, let's see how

const merge = <T1 extends object, T2 extends object>(obj1: T1, obj2: T2): T1 & T2 => {
  return { ...obj1, ...obj2 };
};
Enter fullscreen mode Exit fullscreen mode

Using the extends keyword tells Typescript that the passed generic type should only match this extended type, so no one can pass anything other than an object.

And we can use any type as a constraint
Let's see one more simple example

type MyType = string | number;
const myFunction = <T extends MyType>(x: T) => { // My generic T will  be accepted only if it extends MyType
  return x;
};
Enter fullscreen mode Exit fullscreen mode

We can also set default values to generic types, let's see how

const returnArr = <T = string>(): T[] => [];
returnArr<number>(); // TS knows it'll return an array of numbers
retrunArr(); // TS knows it'll return an array of strings by default

Enter fullscreen mode Exit fullscreen mode

Note: To use one Generic type in a TSX file, you should append a comma for React to understand that it's a generic and not an HTML tag or a component

const arrow = <T,>(x: T) => {
    return x;
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Typescript generics allow us to define reusable functions or classes that work with multiple types rather than a single type.
  • Generics help us define functions or classes once, and expect different behaviors from them.
  • We can pass a single or multiple generic types.
  • We can set default values for our generics.
  • Typescript is smart enough to infer our generics if they're used as parameters, or returned from our function.

Top comments (0)