TypeScript Generics to the Rescue
Like I tried to say before: a Generic is like a variable for our types, which means we can define a variable that represents any type, but that keeps the type information at the same time. That last part is key, because that’s exactly what
any wasn’t doing. With this in mind, we can now re-define our identity function like this:
Keep in mind, the letter used for the name of the Generic can be anything, you can name it however you want. Using a single letter, however, seems to be a standard, so we’re all going with it.
This now allows us to not only define a generic function that can be used with any type but also, the associated variables will contain correct information about the type you’ve chosen. Like this:
Two things to notice about the image:
- I’m specifying the type directly after the function’s name (between < and >). In this case, since the function’s signature is simple enough, we could’ve called the function without this and the compiler would’ve inferred the type from the parameter passed. Nevertheless, if you change the word
stringthen the entire example would no longer work.
- We now can’t print the
lengthproperty, since numbers don’t have it.
This is the behavior you’d expect from a typed language and this is why you want to use Generics when defining generic behavior.
The previous example is commonly known as the “Hello World” of Generics, you’ll find it on every article, but it’s a great way to explain their potential. But there are other interesting things you can achieve, always within the realm of type-safety of course, remember, you want to build things that can be re-used in multiple situations and at the same time, you’re trying to hold on to that type information we care about so much.
Automatic structure checks
This one has got to be my favorite thing to do with generics. Consider the following scenario: you have a fixed structure (i.e an object) and you’re trying to access a property of if dynamically. We’ve all done it before, something like:
I could’ve used
hasOwnProperty or something like that, but you get the point, you need to perform a basic structural check to make sure you control the use case in which the property you’re trying to access does not belong to the object. Now, let’s move this into the type-safety land of TypeScript and see how Generics can help us:
Now, notice how I’m using the Generics notation: I’m not only declaring a generic type K, but I’m also saying it extends the type of a key of Person. This is amazing! You can declaratively state you’re either going to pass a value that matches the strings
city. Essentially you declared an enum, which if you think about it, is less exciting than before. But you don’t have to stop there, you can make this more exciting by re-defining this function like this:
That’s right, we now have TWO generic types, one of them is declared to be extending the keys of the first one, but essentially you’re now no longer restricted to just one type of objects (i.e
Person -type objects) and this function can be used with any of your objects or structures and you know it’ll be safe to use.
And here is what happens if you use it with an invalid property name:
Generics don’t only apply to function signatures, you can also use them to define your own generic classes. This allows for some interesting behavior and the ability to again, encapsulate that generic logic into a re-usable construct.
Here is an example:
With this example, we’re defining a handler that can handle any type of animal, but we can do that without Generics, the benefit here can be seen in the last two lines of the example. This is because with the generic handler logic completely encapsulated inside a generic class, we can then easily constraint the types and create type-specific classes that will only work for either type of animal. You could potentially add extra behavior here as well, and type information would be kept.
Another takeaway from this example is that generic types can be constrained to only extend certain groups. As you can see in the example,
T can only be wither
Horse but nothing else.
This is actually something new that was added in version 4.0 of TypeScript. And although I did already covered it in this article, I’ll quickly review it here.
In a nutshell, what Variadic Tuples allow you to do, is to use Generics to define a variable portion of a tuple definition, which by default, has none.
A normal tuple definition would yield a fixed-size array with predefined types for all of its elements:
type MyTuple = [string, string, number]let myList:MyTuple = ["Fernando", "Doglio", 37]
Now, thanks to Generics and Variadic Tuples, you can do something like this:
If you notice, we used a Generic
T (which extends an array of
unknown) to put a variable section inside the tuple. Since
T is a list of
unknown type, you can put anything in there. You could, potentially, define it to be a list of a single type, like this:
Sky is the limit here, essentially you can define a form of template tuple, which you can use later down the road and extend it however you see fit (with the limitations you set for the template of course).
Generics is a very powerful tool and although sometimes reading code that makes use of them might feel like reading hieroglyphs, it’s just a matter of taking it slow at first. Take your time, parse the code mentally and you’ll start seeing the potential built-in inside it.
So what about you? Have you used Generics in the past? Did I leave a major use for the out? Share it in the comments for others!
See you in the next one!