But it is nothing to be afraid of.
Just as if we were passing in an argument, we pass in the type that we want to use for that specific function call.
Generic types are filled just like how we fill out the function arguments when calling it.
Referring to the above image, when we call identity<Number>(1), the Number type is an argument just like the 1 is.
It populates the value T wherever it appears.
It can also take in multiple types just like we can have multiple arguments.
A function can have multiple generics just like it can have multiple arguments.
Take note of how we are calling the function.
The syntax should start to make sense to you now! There is nothing special about T or U, they are just variable names that we choose.
We populate them with type values when we call the function and it uses those types.
Another way to think of generics is that they transform a function based on the type of data you pass into it.
The animation below shows how the identity function changes with different data types.
A generic will morph into whatever `type` is passed into it.
As you can see, the function takes on whatever type is passed into it, allowing us to create reusable components for different types, just like the documentation promised us.
Definitely take notice of the second console log statement in the animation.
We do not provide a type.
In that case, TypeScript will attempt to infer the type based on the data.
Be careful — type inference only works for simple data.
If you pass in something more complex like an object or multi-type array, it will infer that the type is any, which breaks down our type safety checks.
Generics for Classes and Interfaces Work Exactly the Same as FunctionsWe now know that generics are just a way to pass in types to a component.
We just saw how this works for functions and the good news is: interfaces and classes work exactly the same way! In their case we place the types right after the name of our interface or class name.
See if the following code block makes sense to you.
I hope that it will!If it does not immediately make sense to you, try to trace the type values up the chain of function calls.
It works like this:We instantiate a new instance of IdentityClass, passing in Number and 1.
In the identity class, T becomes Number.
IdentityClass implements GenericInterface<T> and we know that T is Number, so it is as if we are implementing GenericInterface<Number>In GenericInterface, U becomes Number.
I purposefully used different variable names here to show that type value propagates up the chain and the variable name does not matter.
Practical use case: moving beyond primitive typesAll of the examples provided above use primitive types such as Number and string.
These are nice to use as examples, but speaking practically, you are not likely going to be using generics for primitive types.
The real power of generics comes in when we have custom types or classes that form an inheritance tree.
Consider the classic inheritance example of a car.
We have a base class Car which is used as the base for Truck and Vespa.
We then write a utility function washCar which takes in a generic instance of Car and then returns it.
By telling our car wash function that T must extend Car, we know which functions and properties we are able to call within the function.
Using the generic also enables us to return the specific type we pass in, instead of just a non-specific Car.
The output for this code is:Received a Vespa in the car wash.
Cleaning all 2 tires.
Beeping horn – beep beep!Returning your car nowReceived a Truck in the car wash.
Cleaning all 18 tires.
Beeping horn – beep beep!Returning your car nowWrapping upI hope that this post made generics more clear to you! Remember, all you are doing is passing in a type value to the function and nothing more.
:)If you want to learn more about generics, check out the links below.
Further Reading:TypeScript Generics DocumentationTypeScript Generics Explained — a much more in-depth look at generics than my quick primer here provides.