Understanding Generics in Typescript

Understanding Generics in Typescript

I used to think I knew Generics until I saw its application in a codebase and it dawned on me that my knowledge of it may be just recognition based than understanding.

I am writing this article to validate my knowledge of Generics and as well explain to beginners all that Generics is about.

What are Generics

Generics are simply placeholder data types.

So let's say we have an interface below

interface IUSer<T> {
  age:T
}

Such that the age could be a string or a number such that when we want to use the age as a number or a string we tell typescript that T is the type of number or string.

const ageInString: IUser<string> ={ age: "twenty years" }
const ageInNumber: IUser<number> ={ age: 20 }

So with Generics we can create placeholder types and set them to what we want when want to use them.

The Microsoft typescript documentation defines generics as

Code templates that you can define and reuse throughout your codebase. They provide a way to tell functions, classes, or interfaces what type you want to use when you call them.

Generics are usually associated with angle brackets < > as their syntax.

Diving deeper

Let's say we have a function called findLeaders such that it accepts a leader as params and returns a list of leaders.

In normal JS or "any" type of javascript our code will look this way

const findLeaders=(leader: any):any=>{
  return leaders.filter(leader=>leader.isLeader)
}

We can solve the problem of any and properly infer our types without so much repetition using generics.

interface ILeader {
  isLeader: boolean;
  // Other properties of the ILeader interface
}

//With functional
function findLeaders<T>(leaders: T[]): T[] {
  return leaders;
}

findLeaders<ILeader>([{ isLeader: true }]);

However, for Arrow functions from my experience and implementation it is a different ball game to be able to properly make use of Generics in Arrow functions we have to extend what is inherited from the interface With this T copies all the types of ILeader.

//Extend does type Inheritance
const findLeaders = <T extends ILeader>(leaders: T[]): T[] => {
  return leaders;
};

Multiple Generics

Generics in functions could be more than one, we could have <R, S, T> as different place holders.

Let's say we have a function that adds the name, age and house address of a student to a single text/string in typescript.

export function addName<R, S, T>(name: R, age: S, address: T): string {
  return `Hello my name is ${name} I am ${age} years old, I live at ${address}`;
}

addName<string, number, string>("Chibueze", 20, "Game"))

//OUTPUT: Hello my name is Chibueze I am 20 years old, I live at Game

We can also declare Generics with a class

class processName<T, U> {
    private _age: T;
    private _name: U;
    constructor(age: T, name: U) {
        this._age = age;
        this._name = name;
    }
    getName() : T {
        return this._age
    }
}
let user = new processName<number, string>(100, 'Chibueze');
user.getName();      // Displays 'Chibueze'

Conclusion

The provided instance cited above is from my thinking and experimentaion. Numerous possibilities remain available when dealing with Generic types.

To illustrate:

  • You have the option to designate a default type

  • Infuse a degree of organization into your generic types to ensure that the types supplied during utilization possess specific attributes.

  • And numerous other possibilities.

The objective of this article was to introduce Generics and go in details as much I could and explain it properly for beginners to be able to grasp.

For more comprehensive insights, the TypeScript Generics Documentation delves into greater intricacies. It's certainly worth exploring that resource for further information.