
Generics in TypeScript: Getting Together
- Transfer
Hello! TestMace team publishes another translation of the article from the world of web development. This time for beginners! Enjoy reading.
Shatter the veil of mystery and misunderstanding of the syntax
and finally make friends with it
Probably, only seasoned developers of Java or other strongly typed languages do not bang their eyes when they see the generic in TypeScript. Its syntax is fundamentally different from everything that we are used to seeing in JavaScript, so it's not so easy to immediately guess what it does at all.
I would like to show you that in fact, everything is much simpler than it seems. I will prove that if you are able to implement a function with arguments in JavaScript, then you can use generics without any extra effort. Go!
Generics in TypeScript
The TypeScript documentation provides the following definition: "Generics are the ability to create components that work not only with one, but with multiple data types."
Wow! So, the main idea is that generics allow us to create some reusable components that work with various types of data transmitted to them. But how is this possible? That's what I think.
Generics and types are related to each other, like function values and arguments. This is such a way of telling components (functions, classes or interfaces) what type to use when calling them, just like during a call we tell the function which values to use as arguments.
It is best to understand this using the generic of an identical function as an example. An identical function is a function that returns the value of the argument passed to it. In JavaScript, it will look like this:
function identity (value) {
return value;
}
console.log(identity(1)) // 1
Let's make it work with numbers:
function identity (value: Number) : Number {
return value;
}
console.log(identity(1)) // 1
Well, we added a type to the definition of an identical function, but we would like it to be more flexible and work for values of any type, and not just numbers. This is what generics are for. They allow the function to take values of any type of data at the input and, depending on them, transform the function itself.
function identity (value: T) : T {
return value;
}
console.log(identity(1)) // 1
Oh, that weird syntax
! Stop the panic. We just pass the type that we want to use for a specific function call.
Look at the picture above. When you call , type is the same argument as 1. It is substituted everywhere instead . A function can take several types in the same way as it takes several arguments.identity
Number
T
Look at the function call. Now the generic syntax should not scare you. T
and U
- these are just the names of the variables that you assign yourself. When a function is called, the types with which this function will work are indicated instead .
An alternative version of understanding the concept of generics is that they transform the function depending on the specified data type. The animation below shows how the function record and the returned result change when the type is changed.
As you can see, the function accepts any type, which allows you to create reusable components of various types, as promised in the documentation.
Pay special attention to the second call to console.log in the animation above - the type is not passed to it. In this case, TypeScript will try to calculate the type from the transmitted data.
Generic Classes and Interfaces
You already know that generics are just a way to pass types to a component. You just saw how they work with functions, and I have good news: they work in the same way with classes and interfaces. In this case, the type indication follows the name of the interface or class.
Look at an example and try to figure it out for yourself. I hope you succeeded.
interface GenericInterface {
value: U
getIdentity: () => U
}
class IdentityClass implements GenericInterface {
value: T
constructor(value: T) {
this.value = value
}
getIdentity () : T {
return this.value
}
}
const myNumberClass = new IdentityClass(1)
console.log(myNumberClass.getIdentity()) // 1
const myStringClass = new IdentityClass("Hello!")
console.log(myStringClass.getIdentity()) // Hello!
If the code is not immediately understood, try to track the values type
from top to bottom until the function calls. The procedure is as follows:
- A new instance of the class is created
IdentityClass
, and the typeNumber
and value are passed to it1
. - In the class, the value is
T
assigned a typeNumber
. IdentityClass
implements , and we know that - this , and such a record is equivalent to a record .GenericInterface
T
Number
GenericInterface
- In
GenericInterface
genericU
becomesNumber
. In this example, I intentionally used different variable names to show that the type value goes up the chain, and the variable name has no meaning.
Real use cases: go beyond primitive types
In all the above code inserts, primitive types like Number
and were used string
. For examples, this is the most, but in practice, you are unlikely to use generics for primitive types. Generics will be really useful when working with arbitrary types or classes that form an inheritance tree.
Consider a classic example of inheritance. Suppose we have a class Car
that is the basis of Truck
and Vespa
. We write a service function washCar
that takes a generic instance Car
and returns it.
class Car {
label: string = 'Generic Car'
numWheels: Number = 4
horn() {
return "beep beep!"
}
}
class Truck extends Car {
label = 'Truck'
numWheels = 18
}
class Vespa extends Car {
label = 'Vespa'
numWheels = 2
}
function washCar (car: T) : T {
console.log(`Received a ${car.label} in the car wash.`)
console.log(`Cleaning all ${car.numWheels} tires.`)
console.log('Beeping horn -', car.horn())
console.log('Returning your car now')
return car
}
const myVespa = new Vespa()
washCar(myVespa)
const myTruck = new Truck()
washCar(myTruck)
By telling the function washCar
that T extends Car
, we denote which functions and properties we can use inside this function. Generic also allows you to return data of the specified type instead of the usual one Car
.
The result of this code will be:
Received a Vespa in the car wash.
Cleaning all 2 tires.
Beeping horn - beep beep!
Returning your car now
Received a Truck in the car wash.
Cleaning all 18 tires.
Beeping horn - beep beep!
Returning your car now
To summarize
I hope I helped you deal with generics. Remember, all you have to do is just pass the value type
to the function :)
If you want to read more about generics, I have attached a couple of links below.
What to read :
- TypeScript Generics Documentation - Generics Documentation
- TypeScript Generics Explained - a deeper immersion in the topic of generics