TypeScript Generics: A Practical Guide Without the Theory Overload
Introduction
As a developer, I've found that understanding generics in TypeScript can significantly improve the quality and reusability of my code. In this article, I will provide a practical guide to using generics in TypeScript, focusing on when and why to use them.
Basic Generics
Generics allow us to create reusable functions and classes that can work with multiple types. We can define a generic type using the <T> syntax. For example:
function identity<T>(arg: T): T {
return arg;
}
In this example, T is a type parameter that can be replaced with any type. We can call the identity function with a string or a number, and it will return the same type.
Constraints
Sometimes, we want to restrict the type parameter to a specific type or a subset of types. We can use the extends keyword to add constraints to our generic types. For example:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
In this example, K is constrained to be a key of T, which means we can only pass a valid property name as the second argument.
Generic Functions
Generic functions are functions that take type parameters. We can use generic functions to create reusable functions that can work with multiple types. For example:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
In this example, merge is a generic function that takes two objects and returns a new object that combines the properties of both objects.
Generic Classes
Generic classes are classes that take type parameters. We can use generic classes to create reusable classes that can work with multiple types. For example:
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
In this example, Container is a generic class that takes a type parameter T. We can create a Container instance with any type, and it will store and return a value of that type.
Real-World Use Cases
Generics have many real-world use cases. For example, we can use generics to create reusable utility functions, such as a map function that works with arrays of any type:
function map<T, U>(arr: T[], callback: (item: T) => U): U[] {
return arr.map(callback);
}
We can also use generics to create reusable data structures, such as a Stack class that can store elements of any type:
class Stack<T> {
private elements: T[] = [];
push(element: T): void {
this.elements.push(element);
}
pop(): T | undefined {
return this.elements.pop();
}
}
Practical Takeaways
To get the most out of generics in TypeScript, remember the following key points:
- Use generics when you need to create reusable functions or classes that can work with multiple types.
- Use constraints to restrict the type parameter to a specific type or subset of types.
- Use generic functions and classes to create reusable and flexible code.
- Experiment with different use cases to see how generics can improve the quality and reusability of your code.