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];
}
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];
}
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'])
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 };
};
And here's how we would use our function
merge<{ name: string }, { age: number }>({ name: "John" }, { age: 222 });
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
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 };
};
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;
};
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
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;
};
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)