Introduction
Generics in TypeScript enable you to write flexible and reusable code by allowing you to define type parameters for functions, classes, and interfaces, ensuring type safety without compromising on functionality.
Why use generics
Consider the following function passThrough
which simply takes in a value and returns it:
function passThrough(myValue: number | string): number | string {
return myValue;
}
Currently this function takes in a value myValue
which is of a type number
or string
. However, what if we wanted myValue
to be of any type.
We could instead use the any
type as follows:
function passThrough(myValue: any): any {
return myValue;
}
This is not ideal as an any
type means that the function is always going to return an any
type. If we were to pass in a number
, the return type is still going to be an any
type, not a number
type.
Ideally what we’d want is for our function to accept any kind of type and then maintain that type. This is where generics come into the picture. A generic will allow us to specify that a function can take any kind of type, and then this will be maintained throughout the function.
How generic types work
Generics work by adding Type
(or T
) with angled brackets, as seen in the example below:
function passThrough<Type>(myValue: Type): Type {
return myValue;
}
If we were to call this function with a number
type, then our function would return a number
. If we were to call this function with an array type, then our function would return an array type.
In the example above, we used the Type
keyword, however <T>
is more commonly used. For example:
function passThrough<T>(myValue: T): T {
return myValue;
}
Let’s now have a look at the above function in action. In the example below, we are calling our passThrough
function with a variable that has a number
type:
function passThrough<Type>(myValue: Type): Type {
return myValue;
}
const myNumber: number = 10;
passThrough(myNumber);
We can see that our return type is set to the number
type. In other words, our return type is set to the type of the value we are passing into our function.
Practical example
We look at a more practical example below.
We have a User
type that takes in a generic, type User<T>
.
We also have an Address
type.
When we create a new User
, we pass in our Address
type.
type User<T> = {
id: number;
name: string;
details: T;
};
type HomeAddress = {
street: string;
city: string;
postalCode: string;
isApartment: boolean;
};
type WorkAddress = {
street: string;
city: string;
postalCode: string;
companyName: string;
};
const userWithHomeAddress: User<HomeAddress> = {
id: 1,
name: 'Ola',
details: {
street: '123 Main St',
city: 'Oslo',
postalCode: '12345',
isApartment: true
}
};
const userWithWorkAddress: User<WorkAddress> = {
id: 2,
name: 'Kari',
details: {
street: '456 Corporate Blvd',
city: 'Busytown',
postalCode: '67890',
companyName: 'MegaCorp Inc.'
}
};
console.log(userWithHomeAddress.details.isApartment);
// Logs: true
console.log(userWithWorkAddress.details.companyName);
// Logs: "MegaCorp Inc."
With this example, students can see how we can re-use the same User type for different types of address details. Generics allow us to encapsulate and reuse code patterns while still preserving type safety. Instead of creating a completely new type for every combination (e.g., UserWithHomeAddress, UserWithWorkAddress), we can have a single, flexible User type that adjusts based on what we need.
Lesson task
Goal
For the student to be able to demonstrate the use of generics.
Brief
Follow the Level 1 Process as instructed.
NOTE: Lesson tasks do not get submitted on Moodle and are not assessed by tutors. They are mainly there for you to practise what you have learned in the lesson.
Level 1 process
- Create a function called
getTypeOf
. - Give this function a parameter called
currentType
. console.log
thetypeof
of the parameter.- Make the type of the parameter and return type of the function a generic.