Introduction To Generics In TypeScript
Oct 29, 2022 · 5 min readThis post is a simple introduction to generics in TypeScript.
What Is Generics?
Generics is a way of writing code that will work with a wide range of objects and primitives. It’s really useful when we want to write codes that will work with any sort of type, interface, or class definition.
For example, a function to find an element in a list where the elements can be strings, numbers, or any other type. Or maybe you have a Queue class that need to work with any type of primitive. These scenarios are when we need to use generics.
Basic Syntax
Let’s first look at a code snippet that uses generics.
Here, we have a function called print
that is using generics. The function take an argument called val
of type T
.
Note that the T
is only a convention. You can name it whatever you want.
Let’s see how it works when we call the function with arguments of different type.
And the output:
As you can see, the function works with every type. All we have to do is specify a type inside the angle brackets, then all T
inside the function will be replaced with that type.
Take print<number>()
for example. All T
that appear inside the function will be replaced with number
.
And if we give an argument that’s not a number, we’ll get an error.
For example, if we run print<number>("hello")
. We’ll get the following error.
Typescript prevents us from calling a generic function with the wrong type as an argument.
Note that we don’t need to explicitly specify the type the function takes. It can be inferred. For example the code below will still work as expected.
Multiple Generics
We are not limited to one generic in TypeScript. We can use as many as we needs.
For example:
Here, we have a function that take 2 generic types. Let’s see how it works using the code below.
Notice that the 2 generics can also be of the same type as shown at line 3.
Type Constraint
You can see how powerful generics is from the examples above. Our generic function now works with any type. But usually, we want to only allow a specific set of types to be used within our generic. We can accomplish this by constraining the type using the extends
keyword.
For example:
Now we are constraining the T
on the function above to either Array<string>
or Array<number>
. This means that whatever T
is used within our code, it can only be interpreted as either an array of string or an array of number.
If we try to call the function with array of boolean, takeArray([true, false])
, we’ll get the following error.
Note that Array<T>
is a predefined type from the standard TypeScript type definitions.
Generic Constraints
We can also construct a generic type from another generic type.
For example:
Here, our function take two generic types, T
and K
. The type K
is constrained to be the value computed from the keyof
operator on type T
.
The keyof
operator will return a string literal type of the object’s properties. Therefore K
will be constrained to the property names of the type T
.
Let’s look at an example.
The first and second line of the code will produce the following output as expected:
But the third line will result in an error because the property age
doesn’t exist on type keyof T
which is extended by type K
.
Creating A New Object Within Generics
There might be a time when we need to create a factory function that return an instance of a class.
For example:
Surprisingly, the code above results in the following error.
This is happening because the name of T
only exists at compile time. We can use T
for type checking but not for constructing an object of type T
unless we have access to the constructor.
In order to fix the code above, we need to refer to type T
by its constructor function.
With this change, our code will now compile and work as expected.
Wrap Up
That’s it for the introduction to generics in TypeScript 🎉.
Having a good understanding of generics is really important to use the more advanced TypeScript features like Mapped Types), Conditional Types, etc.