Understanding Typescript Generics
Tags: typescript, generics, arrays, javascript
Published: 2020-08-27 11:17:02

_Naturally, Every good software developer looks forward to making their components as reusable as possible. The idea that developers can create reusable components that can be used generally across their entire application, and in a new application is really a big deal as software developers become more concerned about improvements in application development speed, efficiency, and user experience harmonization._
One of the tools for creating such reusable components in Typescript are __Generics__. __Generics__ allow you to be able to create components that can work over a variety of types, rather than a single one. In simpler terms, Generics gives you the power to be able to create an object e.g a function that can accept, return, and work with different data types without losing the information about what the data type was when the function returns.
To start off, let's give an example of a function that takes an array as an argument, and then for each index of the array, adds up the value of the current and preceding indexes. without generics, we would be doing something like this:

```javascript
const performOperation =(acc:number[],currentval:number) : number[]=>{
  if (acc.length === 0){
    acc.push(currentval)  
  } else{    
      acc.push((acc[acc.length-1] + currentval)) 
  }
  return acc;
};
const arrayAdder = (arr: number[]) : number[]=> {
return arr.reduce(performOperation, [])
}
console.log(arrayAdder([2, 3, 4]));

```
Note that without generics, we had to give our functions specific argument and return type. This means that our functions cannot work with any other data type apart from number. a simple solution to this problem might be to describe our functions using the _any type_.
```javascript
const performOperation =(acc:any[],currentval:any) : any[]=>{
  if (acc.length === 0){
    acc.push(currentval)  
  } else{    
      acc.push((acc[acc.length-1] + currentval)) 
  }
  return acc;
};
const arrayAdder = (arr: any[]) : any[]=> {
return arr.reduce(performOperation, [])
}
console.log(arrayAdder([2, 3, 4]));

```
using _any_ to describe our functions is absolutely generic because it allows our functions to accept any and all types of array. But the problem is that <em>we can no longer be sure about the data type when the function  array_adder returns.</em> we only know that if we passed in an array of numbers, anything could be returned.
The solution to this problem, is to be able to capture the data type of the argument and also use it to denote what is to be returned. That is exactly the power that generics gives us.
```javascript
const performOperation = (acc:any[],currentval:any) : any[]=>{
  if (acc.length === 0){
    acc.push(currentval)  
  } else{    
      acc.push((acc[acc.length-1] + currentval)) 
  }
  return acc;
};
const arrayAdder = <T>(arr: T[]) : T[]=> {
return arr.reduce(performOperation, [])
}
```
We have now added a type variable __T__ to the array_adder function. __T__ helps us to capture the data type of the argument the user provides, so as to be able to reuse it. Hence, we say that the array_adder function is __generic__ as it can work over a range of types. Now, our generic function can be called in two ways, either by passing in the type or by argument inference.
```javascript
console.log(arrayAdder<number>([2, 3, 4])); // number is passed as the argument type
console.log(arrayAdder<string>(["l", "o", "l"]));
or
console.log(arrayAdder([2, 3, 4])); // the argument type is determined explicitly
console.log(arrayAdder(["l", "o", "l"]));
```
<h2>Generic Types</h2>
previously, we created functions that worked with a range of data types. Now we will explore the type of the functions, and learn how to create generic interfaces.
```javascript
const arrayAdder = <T>(arr: T[]) : T[]=> {
return arr.reduce(performOperation, [])
}
const myArray: <V>(arr: V[]) => V[] = arrayAdder
console.log(myArray<string>(["h", "e", "l", "l", "o"])) //[ 'h', 'he', 'hel', 'hell', 'hello' ]
```
the above example defines the myArray as being able to hold a function that accepts and returns the same type both specified using the data marker V.
Now, let's go on to write our first generic interface.
```javascript
interface Genericfunction{
<V>(arr: V[]):V[]
}
const arrayAdder = <T>(arr: T[]) : T[]=> {
return arr.reduce(performOperation, [])
}
const myArray: Genericfunction = arrayAdder
console.log(myArray<string>(["h", "e", "l", "l", "o"])) //[ 'h', 'he', 'hel', 'hell', 'hello' ]
```
Also, we could move the generic parameter to be a parameter of the whole interface.
```javascript
interface Genericfunction<V>{
(arr: V[]):V[]
}
const myArray: Genericfunction<string> = arrayAdder
console.log(myArray(["h", "e", "l", "l", "o"])) //[ 'h', 'he', 'hel', 'hell', 'hello' ]
```
Here, instead of describing a generic function, we have a non-generic function that is part of a generic type.So instead of placing the type on the function call signature, we place it on the interface itself.

<h2>Generic Classes</h2>
A generic class is similar to a generic interface. Let's create a generic class called addNumber.
```javascript
class GenericClass<T>{
addNumber: (a:T, b:T)=> T
}
let myGenericNumber = new GenericClass<number>()
myGenericNumber.addNumber = (x, y) => x + y;

```
Just like in interfaces, adding a type parameter on the class ensures that all properties of the class are working with the same type.

<h2>Conclusion</h2>
Generics is a handy tool when it comes to writing reusable and maintainable code. In the long term, not only will you benefit from it, but your whole team will. your codebase becomes readable, and maintainable. that means other developers can easily understand and maintain it if needed as it makes your application more consistent.