Master TypeScript Generics
Generics is a concept in programming that allows us to create reusable code. I will first talk about what generics are and then I will show some simple and advanced examples to enforce what we've learned.
If you wish to see a real life example of generics in a React application, make sure to check out my video below:
What are Generics
Creating Resuable Code in JavaScript
In JavaScript you can write functions and classes and pass arguments to them to customize the functionality of these functions and classes. For example, instead of writing the function below:
function readTextFile(textFileName) {
textFileReader.read(texFileName)
}
readTextFile('file.txt'); // will work fine
readTextFile('file.csv'); // error
readTextFile('file.docx'); // error
We can write a more generic function that can work with any file instead of working with only text files:
function readAnyFile(anyFileName) {
anyFileReader.read(anyFileName)
}
readAnyFile('file.txt'); // will work fine
readAnyFile('file.csv'); // will work fine
readAnyFile('file.docx'); // will work fine
Creating Resuable Code in TypeScript
While there are many ways to create reusable code in TypeScript, I will focus only on Generics in this post.
In TypeScript, not only we can pass arguments to functions or class instances, but also we can pass different types as generics to customize the functions and the classes. So instead of writing an implementation of an array of numbers and array of string for example, we can write a more general ArrayOfAnything
class and customize it with generics:
Generics in Classes
class ArrayOfNumbers() {
constructor(private collection: number[]) {}
get(index: number): string {
return this.collection[index]
}
}
class ArrayOfStrings() {
constructor(private collection: string[]) {}
get(index: string): string {
return this.collection[index]
}
}
const arrayOfStrings = new ArrayOfStrings(['1', '2']);
const arrayOfNumbers = new ArrayOfNumbers([1, 2]);
we can write one general class instead, and pass a generic type called T
to the class that can be changed based on we instantiate the class. The syntax is as shown in the example:
class ArrayOfAnything<T> {
constructor(private collection: T[]) {}
get(index: number): T {
return this.collection[index]
}
}
const arrayOfStrings = new ArrayOfAnything<string>(['1', '2']);
const arrayOfNumbers = new ArrayOfAnything<number>([1, 2]);
const arrayOfBooleans= new ArrayOfAnything<boolean>([true, false]);
Generics in Functions
Let's see examples of generics with functions. Instead of writing repetitive code like this:
function printStrings(arr: string[]) {
arr.forEach(str => {
console.log(str)
});
}
function printNumbers(arr: number[]) {
arr.forEach(num => {
console.log(num)
});
}
we can simply write:
function printElements<T>(arr: T[]): void {
arr.forEach(element => {
console.log(element)
});
}
printElements(['1', '2', '3']); // element is inferred by TS as 'string'
printElements([1, 2, 3]); // element is inferred by TS as 'number'
The Generic Type
T
is not a special letter. We can pass anything, but the letterT
is coomnoly used in codebases.
Generics vs. 'any'
At this point you might ask yourself, 'why don't we simply use the keyword any
?' The answer is for two reasons:
We need to avoid the
any
keyword in TypeScript because that will disarm us from leveraging the power of TypeScript and our code will become JavaScript not TypeScript.Although generics can be subsituted by any type passed to the function or class, they are more specific than
any
. The diagram below shows the difference:TypeScript cannot differntiate between the different types of variables and arguments when they have the type
any
. But TypeScript can successfully infer the types of variables and arguments when they are typed with a generic instead ofany
.
function printElements(arr: any[]): void {
arr.forEach(element => {
console.log(element)
});
}
printElements(['1', '2', '3']); // element is inferred by TS as 'any'
printElements([1, 2, 3]); // element is inferred by TS as 'any'
Generics Constraints
Let's assume we have the code below:
You can see that TypeScript is complaining with this error:
That's very expected because I simply can pass any array, like an array of numbers, and TypeScript here is playing it safe and assuming the worst case scenario. So we need to tell TypeScript that T
is not any generic, but a specific type of generic. To do that we define an interface Printable
to tell TypeScript that the generic T
extends the properties of that interface as follows
Let's see a more advanced syntax on generics constraints:
In this code snippet, I can pass any string to the user.getKey()
method and TypeScript will have no problem in detecting that we are trying to access a key that doesn't exist. Using generics, we can refine our code and take full advantage of TypeScript's type checking system, and tell TypeScript that the argument we are passing to the getKey()
function is not only a string, but a specific type of string:
and now TypeScript is complaining for good:
Let's break down the syntax.
In JavaScript and TypeScript, all object keys are strings behind the scenes. Even if you can set a number as an object key, behind the scene JavaScript transforms it into a string. So for this reason, TypeScript understands that keyof T
is a specific string, not just any string, and that's why we are getting these errors because TypeScript now expects us to pass only strings that are keys of the initial object we created when instantiating the class.
In the examples above I demonstrated generics in functions and classes, but they can be used with interfaces and methods in similar ways.
You've learned about generics, and how they are used as type parameters for classes and functions, and how they are used to make more specific types of general types like any
or string
.
Subscribe to my newsletter
Read articles from Mark Maksi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mark Maksi
Mark Maksi
Full Stack JS Engineer | Content Creator | I'm on a personal mission to provide high quality content on building web apps from code to production through my blog and my YouTube channel.