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}45printNum(12)6printNum(92)7printNum(2)89// result10// 1211// 9212// 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 }34 return newUser5}67const user = createNewUser({ name: 'John Doe', age: 21 })89console.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: string3 age: number4}56const createNewUser = (user: User) => {7 const newUser = { ...user, active: true, power: 100 }89 return newUser10}1112const user = createNewUser({ name: 'John Doe', age: 21 })1314console.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 }34 return newUser5}67const user = createNewUser({ name: 'John Doe', age: 21 })89console.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 }34 return newUser5}67const user = createNewUser({ name: 'John Doe', age: 21 })89console.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 }34 return newUser5}67interface User {8 name: string9 age: number10}1112const user = createNewUser<User>({ name: 'John Doe', age: 21 })1314interface User2 extends User {15 country: string16}1718const 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: string3 age: number4 extraInfo: T5}
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: string3 age: number4 extraInfo: T5}67interface Address {8 city: string9 country: string10}1112const 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: string3 age: A4 extraInfo: T5}67const 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: string3 age: A4 extraInfo: T5}67const 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
- Email: thatanjan@gmail.com
- LinkedIn: @thatanjan
- Portfolio: anjan
- Github: @thatanjan
- Instagram : @thatanjan
- Twitter: @thatanjan
Blogs you might want to read:
- Eslint, prettier setup with TypeScript and react
- What is Client-Side Rendering?
- What is Server Side Rendering?
- Everything you need to know about tree data structure
- 13 reasons why you should use Nextjs
- Beginners guide to quantum computers
Videos might you might want to watch: