In TypeScript, generics provide a way to create reusable components that can work with a variety of types while maintaining type safety. However, sometimes you may want to restrict the types that can be used with a generic. This is where type constraints come in. Type constraints allow you to specify that a generic type must extend a certain interface or class, ensuring that only compatible types are used.
Type constraints are specified using the extends keyword after the generic type parameter. By doing this, you can ensure that the generic type has at least the properties and methods defined in the constraint. This is particularly useful when you want to access specific properties or methods on a generic type without causing type errors.
For example, consider a function that logs the length of an array. If you use a generic type parameter without constraints, TypeScript won't know if the type has a length property. However, by adding a constraint to ensure the type extends ArrayLike, you can safely access the length property.
Let's start with a simple example where we create a function that logs the length of an array:
1function logLength<T>(arr: T[]): void {2console.log(arr.length);3}45logLength([1, 2, 3]); // Output: 36logLength(['a', 'b', 'c']); // Output: 3
In this example, the generic type T is constrained to be an array. However, if we try to pass a non-array type, TypeScript will throw an error:
1logLength('hello'); // Error: Argument of type 'string' is not assignable to parameter of type 'never[]'.
To fix this, we can add a constraint to ensure T extends ArrayLike<T>:
1function logLength<T extends ArrayLike<T>>(arr: T): void {2console.log(arr.length);3}45logLength([1, 2, 3]); // Output: 36logLength('hello'); // Output: 5
Now, the function can accept both arrays and strings, as they both have a length property.
Type constraints can also be used to enforce more complex conditions. For instance, you might want to ensure that a generic type has a specific method:
1interface HasPrint {2print(): void;3}45function executePrint<T extends HasPrint>(obj: T): void {6obj.print();7}
In this example, the executePrint function requires that the generic type T implements the HasPrint interface. This ensures that any object passed to the function has a print method:
1class Document implements HasPrint {2print(): void {3console.log('Printing document...');4}5}67const doc = new Document();8executePrint(doc); // Output: Printing document...
If you try to pass an object that does not implement the HasPrint interface, TypeScript will throw an error:
1class Image {2display(): void {3console.log('Displaying image...');4}5}67const img = new Image();8executePrint(img); // Error: Argument of type 'Image' is not assignable to parameter of type 'HasPrint'.
You can also apply multiple constraints by separating them with &. This ensures that the generic type satisfies all specified conditions:
1interface HasName {2name: string;3}45interface HasAge {6age: number;7}89function displayInfo<T extends HasName & HasAge>(person: T): void {10console.log(`Name: ${person.name}, Age: ${person.age}`);11}
In this example, the displayInfo function requires that the generic type T implements both HasName and HasAge interfaces:
1class Person implements HasName, HasAge {2name: string;3age: number;45constructor(name: string, age: number) {6this.name = name;7this.age = age;8}9}1011const person = new Person('Alice', 30);12displayInfo(person); // Output: Name: Alice, Age: 30
If the object does not satisfy both constraints, TypeScript will throw an error:
1class Employee implements HasName {2name: string;34constructor(name: string) {5this.name = name;6}7}89const employee = new Employee('Bob');10displayInfo(employee); // Error: Argument of type 'Employee' is not assignable to parameter of type 'HasName & HasAge'.
In the next section, we will explore Modules in TypeScript, which allow you to organize your code into reusable and maintainable modules. Modules help manage dependencies and improve the structure of larger applications.
Stay tuned for more advanced topics and best practices in TypeScript!