TypeScript Generic Constraints
In TypeScript, generic constraints restrict the types that can be used with a generic type by using the extends keyword. This ensures that the generic type adheres to a specific structure or interface, allowing access to certain properties or methods within the generic type.
What are Generic Constraints?
- TypeScript Generics allows us to write reusable code by working with different data types. We can create functions, classes, or interfaces that can work with different data types.
- Generics are defined as <T> and This type of T is used to define the type of function arguments, return values, etc.
- Generic Constraints are used to specify limits to the types that can be used with generic type parameters.
- This results in type checking and these conditions ensure that variables have a certain type of value before they are used in the context.
- This check minimizes the occurrence of runtime errors.
Syntax:
function genericConstraintFunction<T extends GenericConstraintType>(param: T): void {
// ...
}
Where-
- T is the generic type parameter.
- `extends` GenericConstraintType specifies the constraint that Type T should be extending GenericConstraintType type.
Example 1: In this example, the Sports interface has a name property. The printSportName function uses extends to ensure its argument conforms to the Sports type before execution.
// Define Sports interface with a name property
interface Sports {
name: string;
}
// Function to print sport name, ensuring T extends Sports
function printSportName<T extends Sports>(sport: T): void {
console.log(sport.name);
}
// Create a sport object of type Sports
let sport: Sports = { name: "baseball" };
// Call function with sport object
printSportName(sport);
Output:
baseball
Example 2: In this example, we use extends keyof to ensure that the generic type parameter K is a key of type T. This enforces that K is a valid property of T.
interface Sports {
name: string;
players: number;
}
function getNumberOfPlayers<T extends Sports, K extends keyof T>
(sport: T, players: K): T[K] {
return sport[players];
}
let sport: Sports = { name: "baseball", players: 9 };
// 'players' is inferred as a number
let players: number = getNumberOfPlayers(sport, 'players');
console.log(`Number of Players are : ${players}`);
Output:
Number of Players are : 9
Example 3: In this example, we ensure that the generic type parameter class object implements a specific interface.
interface Sports {
name: string;
players: number;
getNumberOfGloves(): number;
}
class Baseball implements Sports {
constructor(public name: string, public players: number) { }
getNumberOfGloves(): number {
return this.players * 2;
}
}
function getNumberOfGloves<T extends Sports>(sport: T): void {
console.log(`Number of Gloves required are :
${sport.getNumberOfGloves()}`);
}
let baseball = new Baseball("baseball", 9);
getNumberOfGloves(baseball);
Output:
Number of Gloves required are : 18