Generics in TypeScript allow you to create reusable components that can work with different types. Instead of specifying a single type, you use a type variable that can represent any type.
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString");
let output2 = identity<number>(100);
console.log(output1);
console.log(output2);
In the above example, the identity
function uses a generic type variable T
. This function can accept an argument of any type and return the same type.
Constraints are used to limit the types that a generic type variable can represent. You can use the extends
keyword to specify a constraint.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity("hello");
// loggingIdentity(10); // This will cause a compilation error because number does not have a length property
In this example, the loggingIdentity
function has a generic type variable T
that is constrained to types that have a length
property.
You can use multiple generic type variables in a single function or class.
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let result = pair<string, number>("hello", 123);
console.log(result);
In this example, the pair
function uses two generic type variables T
and U
to create a tuple of two different types.
The keyof
operator can be used in generic constraints to restrict a type variable to the keys of another type.
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
console.log(getProperty(x, "a"));
// console.log(getProperty(x, "m")); // This will cause a compilation error because "m" is not a key of x
Here, the type variable K
is constrained to the keys of type T
.
You can create generic classes in TypeScript.
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
console.log(myGenericNumber.add(5, 10));
In this example, the GenericNumber
class is a generic class that can work with different number types.
Interfaces can also be generic.
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
console.log(myIdentity(456));
Here, the GenericIdentityFn
is a generic interface that describes a function with a generic type parameter.
Make sure that the constraints you apply are appropriate for the functionality of your code. Over - constraining can limit the reusability of your generic code, while under - constraining can lead to type - safety issues.
Use meaningful names for your generic type variables. For example, instead of using T
, you can use Item
if your generic function or class is dealing with items.
function processItems<Item>(items: Item[]): Item[] {
return items;
}
TypeScript advanced generics and constraints are powerful tools that allow developers to write highly reusable and type - safe code. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can leverage these features to build more robust and maintainable applications. Whether you are working on a small project or a large - scale enterprise application, generics and constraints can significantly improve the quality of your TypeScript code.