Hello! The TestMace team publishes a regular translation of an article from the world of web development. This time for newbies! Enjoy reading.
Dispel the veil of mystery and misunderstanding over the syntax <T>
and finally make friends with it
Probably only experienced developers of Java or other strongly typed languages do not blink when they see a generic in TypeScript. Its syntax is fundamentally different from all that we used to see in JavaScript, so it’s so difficult to guess what it does at all.
I would like to show you that everything is actually 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!
The TypeScript documentation provides the following definition: "generics are the ability to create components that work not only with one, but with several data types."
Great! So, the basic idea is that generics allow us to create some reusable components that work with different types of data transferred to them. But how is this possible? That's what I think.
Generics and types are related to each other, like the values and arguments of a function. This is such a way to tell components (functions, classes or interfaces) what type you need to use when calling them, just like during a call we tell functions which values to use as arguments.
It is best to analyze this by the example of a generic identity function. The identity 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 the identical function, but we would like it to be more flexible and work for values of any type, not just numbers. This is what the generics are for. They allow functions to take values of any data type at the input and, depending on them, convert the function itself.
function identity <T>(value: T) : T { return value; } console.log(identity<Number>(1)) // 1
Oh, this weird <T>
syntax! Set aside the panic. We just pass the type we want to use for a particular function call.
Look at the picture above. When you call identity<Number>(1)
, the Number
type is the same argument as 1. It is substituted instead of T
everywhere. A function can take several types in the same way that it takes several arguments.
Look at the function call. Now, the generics syntax shouldn't scare you. T
and U
are simply the names of the variables that you assign yourself. When calling a function, the types with which the function will operate 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's entry changes and the result returned when the type changes.
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 particular attention to the second console.log call on the animation above - the type is not passed to it. In this case, TypeScript will attempt to compute the type from the transferred data.
You already know that generics are just a way to transfer types to a component. You have just seen how they work with functions, and I have good news: they work with classes and interfaces in exactly the same way. In this case, an indication of the types follows the name of the interface or class.
Look at the example and try to figure it out for yourself. I hope you did it.
interface GenericInterface<U> { value: U getIdentity: () => U } class IdentityClass<T> implements GenericInterface<T> { value: T constructor(value: T) { this.value = value } getIdentity () : T { return this.value } } const myNumberClass = new IdentityClass<Number>(1) console.log(myNumberClass.getIdentity()) // 1 const myStringClass = new IdentityClass<string>("Hello!") console.log(myStringClass.getIdentity()) // Hello!
If the code is not immediately clear, try to track the type
from top to bottom down to function calls. The procedure is as follows:
IdentityClass
class is created, and the type of Number
and the value 1
are passed to it.T
assigned the type Number
.IdentityClass
implements GenericInterface<T>
, and we know that T
is Number
, and this record is equivalent to writing GenericInterface<Number>
.GenericInterface
generic U
becomes Number
. In this example, I intentionally used different variable names to show that the value of the type goes up the chain, and the name of the variable has no value.In all the above code inserts, primitive types like Number
and string
were used. For examples, the fact is, but in practice you are unlikely to begin using generics for primitive types. Generics will be truly useful when working with arbitrary types or classes that form an inheritance tree.
Consider the classic example of inheritance. Suppose we have a class Car
, which is the basis of the classes Truck
and Vespa
. We write the service function washCar
, which takes a generic instance of 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 <T extends Car> (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<Vespa>(myVespa) const myTruck = new Truck() washCar<Truck>(myTruck)
By washCar
function 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 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
I hope I helped you deal with generics. Remember, all you have to do is just pass the type
value to the function :)
If you want to read more about generics, I attached a couple of links further.
What to read :
Our team creates a cool TestMace tool - a powerful IDE for working with APIs. Create scripts, test endpoints and use all the power of advanced autocompletion and syntax highlighting. Write to us! We are here: Telegram , Slack , Facebook , Vk
Source: https://habr.com/ru/post/455473/
All Articles