Typescript Generics explained easily in Cules Coding by @thatanjan

Typescript Generics explained easily

In this article, I will explain the basics of generics in typescript.

Generics in typescript can be confusing but it is very easy. It is a great and useful feature. I will explain it as simply as possible.

Video Tutorial

What is Generics?

Let me give you a simple example:

1const printNum = (num: number) => {
2 console.log(num)
3}
4
5printNum(12)
6printNum(92)
7printNum(2)
8
9// result
10// 12
11// 92
12// 2

This function takes a number as a parameter and prints it on the console. The num parameter can be anything. We don't know. Whenever you call the function with an argument, the num parameter becomes that argument value.

Typescript generics is kinda like that. But instead of expecting values as parameters, we expect types. And we pass types as arguments. I think typescript generics name is kinda misleading. This name makes it look complex. But it is not. It should be called type parameter or something like that.

Let's see how it works:

1const createNewUser = (user: object) => {
2 const newUser = { ...user, active: true, power: 100 }
3
4 return newUser
5}
6
7const user = createNewUser({ name: 'John Doe', age: 21 })
8
9console.log(user)
10console.log(user.name) // error: Property 'name' does not exist on type '{ active: boolean; power: number; }'.

This function takes a user parameter which has to be an object. We are creating a new user object with the properties of the user object and adding the active and power properties. The new user object is returned.

We are creating a new user with that function passing a user object as an argument. Finally, we are printing the user object.

We don't have any errors. But if we try to access the name for age property, we will get an error like this:

1Property 'name' does not exist on type '{ active: boolean; power: number; }'.

Because the user parameter has object type but we didn't specify any properties and their type. So the compiler didn't know what types to include.

We can fix this by specifying an interface:

1interface User {
2 name: string
3 age: number
4}
5
6const createNewUser = (user: User) => {
7 const newUser = { ...user, active: true, power: 100 }
8
9 return newUser
10}
11
12const user = createNewUser({ name: 'John Doe', age: 21 })
13
14console.log(user)
15console.log(user.name) // works fine

But different users might need different properties. So, if you use a single interface then you can't use it for different users.

This is where Typescript generics come into play. We can expect a type parameter for the function. And also we will be able to pass the type when we will call the function.

1const createNewUser = <T>(user: T) => {
2 const newUser = { ...user, active: true, power: 100 }
3
4 return newUser
5}
6
7const user = createNewUser({ name: 'John Doe', age: 21 })
8
9console.log(user)
10console.log(user.name) // works fine

We add type parameters(generics) to the function by adding <> after the function name. Then you can specify your type parameter name. You can call it whatever you want but most people use T for simplicity.

Then we have specified the user as type T. Now whatever argument you will pass in the function call, user will be of that type. You can pass whatever type you want. But if you want T to be a specific type, then you can extend that type.

1const createNewUser = <T extends object>(user: T) => {
2 const newUser = { ...user, active: true, power: 100 }
3
4 return newUser
5}
6
7const user = createNewUser({ name: 'John Doe', age: 21 })
8
9console.log(user)
10console.log(user.name) // works fine

Now you always have to pass an object. You can also pass types in the function call. What if you always want to pass some interface?

1const createNewUser = <T>(user: T) => {
2 const newUser = { ...user, active: true, power: 100 }
3
4 return newUser
5}
6
7interface User {
8 name: string
9 age: number
10}
11
12const user = createNewUser<User>({ name: 'John Doe', age: 21 })
13
14interface User2 extends User {
15 country: string
16}
17
18const user2 = createNewUser<User2>({
19 name: 'John Doe',
20 age: 21,
21 country: 'BD',
22})

This time, we are creating two user interfaces and we are passing the user interface as an argument. And those two user object will be their interface type.

I hope everything is clear to you now. If not, let me give you another example.

1interface User<T> {
2 name: string
3 age: number
4 extraInfo: T
5}

We have this User interface and extraInfo property can be any type. We just don't know. But don't pass any type.

So that's why we are using generics.

1interface User<T> {
2 name: string
3 age: number
4 extraInfo: T
5}
6
7interface Address {
8 city: string
9 country: string
10}
11
12const user: User<Address> = {
13 name: 'Anjan',
14 age: 20,
15 extraInfo: {
16 city: 'Dhaka',
17 country: 'BD',
18 },
19}

Now extraInfo is of type Address.

Multiple types in the generics.

1interface User<T, A> {
2 name: string
3 age: A
4 extraInfo: T
5}
6
7const user: User<Address, number> = {
8 name: 'Anjan',
9 age: 20,
10 extraInfo: {
11 city: 'Dhaka',
12 country: 'BD',
13 },
14}

Default types

1interface User<T, A = number> {
2 name: string
3 age: A
4 extraInfo: T
5}
6
7const user: User<Address> = {
8 name: 'Anjan',
9 age: 20,
10 extraInfo: {
11 city: 'Dhaka',
12 country: 'BD',
13 },
14}

Shameless Plug

I have made an Xbox landing page clone with React and Styled components. I hope you will enjoy it. Please consider like this video and subscribe to my channel.

That's it for this blog. I have tried to explain things simply. If you get stuck, you can ask me questions.

Contacts

Blogs you might want to read:

Videos might you might want to watch:

Next Post10 reasons why you should use Typescript