An Additional Generics Example
Explore an additional example of using TypeScript generics to see how they can also be used to restrict the types that certain functions allow.
We'll cover the following
In this lesson, we’ll see how generics can help restrict the parameter types accepted by functions.
Let’s start by defining a simple class called Animal
.
class Animal {public legCount: number;constructor(legCount: number) {this.legCount = legCount}}
We’ll then define two more classes, Cat
and Kangaroo
that extend
class Animal
:
class Cat extends Animal {constructor() {super(4);}}class Kangaroo extends Animal {constructor() {super(2);}}
Finally, let’s make a Bacteria
class, which does not extend the Animal
class. We can leave it empty for now:
class Bacteria {}
Let’s imagine we want to write a function legPrinter
that prints the legs of any animal. We don’t want to write a separate function for, say, a printCatLegCount
and a printKangarooLegCount
.
We just want one function. Again, generics provides a way for our code to accept types that have a legCount
and can be used by our printLegCount
function.
Let’s start by writing the skeleton of our printLegCount
function.
function printLegCount() {}
The extends
operator
In this situation, passing a plain generic type <T>
to print isn’t enough. We don’t want just any type to be accepted by printLegCount
. Luckily, TypeScript generics support the built-in JavaScript operator extends. We can use it to include any type T
that extends the Animal
class. In other words, we can allow any type T
that is a child or subclass of the Animal
class. The syntax looks like this:
function printLegCount<T extends Animal>(animal: T) {}
Finally, filling out the body of printLegCount
, we have the complete typed function:
function printLegCount<T extends Animal>(animal: T) {console.log(animal.legCount)}
Testing printLegCount
We can test the Intellisense by passing an instance of each of our Cat
, Kangaroo
, and Bacteria
classes into printLegCount
, and seeing if TypeScript complains. Check out the interactive example of everything we have written in this lesson:
class Animal {public legCount: number;constructor(legCount: number) {this.legCount = legCount}}class Cat extends Animal {constructor() {super(4);}}class Kangaroo extends Animal {constructor() {super(2);}}class Bacteria {}function printLegCount<T extends Animal>(animal: T) {console.log(`My leg count is: ${animal.legCount}`);}const myCat = new Cat();const myKangaroo = new Kangaroo();// Both fine: myCat and myKangaroo are of the Cat and Kangaroo// type respectively, which both extend AnimalprintLegCount(myCat);printLegCount(myKangaroo);const myBacteria = new Bacteria();// TypeScript complains here, since the Bacteria class does not extend animal!// Comment out this line to see the output of the two function calls above.//printLegCount(myBacteria);
So, not only does our function printLegCount
work correctly from a functional standpoint because it correctly prints the legCount
of a given Animal
, it also works from a type perspective by accepting only parameters of types that extend the Animal
class!
Conclusion
In this chapter, we scaffolded our application with create-react-app
's TypeScript template. This gave us a good playground to work in, which we’ll use in upcoming lessons to learn more about generics. Then, we took our first look at generics in TypeScript by building a basic generic sortByKey
function. Finally, we learned that generics can also be used to restrict the types that certain functions allow.
TypeScript generics basics, extends, and generics advantages
What is one way of phrasing T extends Animal
?
All T
types that are of a child class or subclass of the Animal
class.
All T
types that implement functionality beyond the Animal
class
All T
types that work as a plugin to class Animal
, extending class Animal
's abilities and behaviour.