TypeScript Generics: A Practical Guide Without the Theory Overload
Introduction
As a developer, I've found that understanding TypeScript generics can be a bit overwhelming due to the amount of theory involved. In this article, I'll provide a practical guide to using TypeScript generics, focusing on when and why to use them.
Basic Generics
Generics in TypeScript allow us to create reusable functions and classes that can work with multiple types. We can think of generics as a way to pass types as arguments to functions or classes. Here's a simple example of a generic function:
function first<T>(arr: T[]): T {
return arr[0];
}
In this example, T is a type parameter that represents the type of the array elements. We can use this function with arrays of any type, such as numbers or strings.
Constraints
Sometimes, we want to restrict the types that can be used with our generic functions or classes. We can do this using constraints. For example, let's say we want to create a function that works only with objects:
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 it can only be a property name of the object.
Generic Functions
Generic functions are useful when we need to perform operations that work with multiple types. Here's an example of a generic function that merges two objects:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
In this example, T and U are type parameters that represent the types of the two objects being merged.
Generic Classes
Generic classes are useful when we need to create classes that can work with multiple types. Here's an example of a generic class that represents a stack:
class Stack<T> {
private elements: T[] = [];
push(element: T): void {
this.elements.push(element);
}
pop(): T | undefined {
return this.elements.pop();
}
}
In this example, T is a type parameter that represents the type of the elements in the stack.
Real-World Use Cases
TypeScript generics have many real-world use cases, such as creating reusable utilities, working with data structures, and implementing design patterns. Here are a few examples:
- Creating a reusable
filterfunction that works with arrays of any type - Implementing a
Mapdata structure that can work with any key and value types - Creating a generic
Repositoryclass that can work with any data model
Practical Takeaways
When working with TypeScript generics, keep the following in mind:
- Use generics when you need to create reusable functions or classes that can work with multiple types.
- Use constraints to restrict the types that can be used with your generic functions or classes.
- Use generic functions when you need to perform operations that work with multiple types.
- Use generic classes when you need to create classes that can work with multiple types.
- Consider using TypeScript generics when working with data structures, design patterns, or reusable utilities.