TypeScript: what are generic types?

Subscribe to my newsletter and never miss my upcoming articles

After some time working with the TypeScript language, venturing into other programmers' code, and even using some famous frameworks, you may have come across code snippets similar to this...

interface Collection<T> {...}

...and thought: after all, what is this T? How do I use it in my code? 🤔

Well, the first point to be considered is that T there could be any other letter or name in its place. If I want to do this, I can:

interface Collection<ModelType> {...}

However, by convention, when we create a generic type, it is common to refer to it only by the letter T because, in the example above, ModelType looks more like an existing type than a generic type exclusive to the interface where it is being used.

Now let's see how to use this TypeScript feature.

What exactly does a generic type represent?

Suppose you want to create a function that will take an argument of any type and return a value of that type. Perhaps the solution that comes to mind is the following:

function doSomething(arg: any): any {...}

But pay attention to one detail: while you are saying that you can receive any kind of value as an argument, you are also defining that any kind of value can be returned!

That is, I can pass one string as an argument and receive a value type number, object, array, or anything else. But in our example, this is not what we want. We hope to receive a kind of value equal to what we spent. What is the solution to this?

function doSomething<T>(arg: T): T {...}

In the code above, we created a generic type T and defined that both the argument and the function's return will be of this type. T, here, represents any type of value. However, once this type is defined, it can no longer be changed. This means that when we insert a string as argument, TypeScript will automatically know that the function's return will be a string too.

When to use?

There are different ways to use generic types. The important thing, therefore, is whether they are needed or not.

Generic types are useful when you want to ensure that, while your code has the versatility to work with any type of value, it also sticks only to this value, to avoid confusion.

Imagine, for example, that you want to create a function that makes requests PATCH to the server, sending only parts of model instances that exist in your code. In this case, the same function will work with different types of objects. So, it would make sense to use the following code:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

function updateInstance<T>(obj: Partial<T>, id: number): Partial<T> {...}

First, we create a type called Partial, which represents part of any object (T). Just to exemplify, we could do this:

interface Product {
  id?: number;
  name: string;
  price: number;
}

let partialProduct: Partial<Product> = { price: 19 }

Then, we create a function with a generic type, which will be used to determine what type will be the partial object that we pass as an argument.

We could not do this, for example:

updateInstance<Product>({ anotherProperty: 'testing' }, 2); // error

The compiler would show us an error, as anotherProperty does not exist in the type Product.

But if we do this, there will be no problem:

updateInstance<Product>({ name: 'Computer' }, 2); // ok

More examples

See some other ways to use generic types.

In interfaces

In this case, a view that could render a list of objects of a certain type, giving the user the option to select one of them.

interface ContentView<T> {
  itemsList: T[];
  selectedItem: T;
}

In classes

Here, a class that would allow us to make requests to the server based on a given type, such as Product, User, or Post.

class Request<T> {

  constructor() {  }

  get(): T[] {...}
  getById(id: number): T {...}
  post(obj: T): T {...}
  put(obj: T, id: number): T {...}
}

In functions

A function that would work with two different generic types for its arguments.

function doSomething<T1, T2>(arg1: T1, arg2: T2): void {...}

Conclusion

Use generic types when you are willing to accept any type, whereas once defined, your code accepts to work only with it.

I strongly recommend that you read the documentation on generic types on the TypeScript website. There, you will see more examples that will reinforce your knowledge and you will also learn to use advanced types. 😸

Buy me a coffee

No Comments Yet