JavaScript > TypeScript > TypeScript Basics > Generics
TypeScript Generics: Creating Reusable Components
Learn how to use generics in TypeScript to create reusable components that can work with a variety of data types while maintaining type safety.
Introduction to Generics
Generics in TypeScript allow you to write code that can work with a variety of types while still maintaining type safety. They are essentially type variables that allow you to capture the type provided by the user and use it later. This avoids having to write multiple functions or classes for each type you want to support. Without generics, you might resort to using the any
type, which sacrifices type safety, or creating separate functions for each type, which leads to code duplication and is not scalable.
Simple Generic Function
This example demonstrates a simple generic function called identity
. It takes an argument of type T
and returns a value of the same type. The <T>
syntax introduces a type variable T
, which can be any type specified when the function is called. When calling the function, you can explicitly specify the type argument using angle brackets, like identity<string>("hello")
, or TypeScript can infer the type based on the argument passed, as shown with identity(42)
.
function identity<T>(arg: T): T {
return arg;
}
let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(42);
console.log(myString); // Output: hello
console.log(myNumber); // Output: 42
Generic Interface
Generics can also be used with interfaces. Here, we define an interface Box
that takes a type parameter T
. Any property using T
will then use the specified type when the interface is implemented. In the example, numberBox
is a Box
of numbers and stringBox
is a Box
of strings, both maintaining type safety.
interface Box<T> {
value: T;
}
let numberBox: Box<number> = { value: 10 };
let stringBox: Box<string> = { value: "TypeScript" };
console.log(numberBox.value); // Output: 10
console.log(stringBox.value); // Output: TypeScript
Generic Class
This code defines a class DataHolder
that uses a generic type T
to hold data. The constructor accepts a value of type T
, and the getData
method returns the stored data, also of type T
. When creating instances of the class, you specify the type within angle brackets, e.g., DataHolder<number>
or DataHolder<string>
.
class DataHolder<T> {
private data: T;
constructor(data: T) {
this.data = data;
}
getData(): T {
return this.data;
}
}
let numberHolder = new DataHolder<number>(100);
let stringHolder = new DataHolder<string>("Generics");
console.log(numberHolder.getData()); // Output: 100
console.log(stringHolder.getData()); // Output: Generics
Generic Constraints
Generic constraints allow you to limit the types that can be used with a generic. In this example, the Lengthy
interface defines that the type must have a length
property of type number
. The logLength
function uses <T extends Lengthy>
, which means that the generic type T
must satisfy the Lengthy
interface (i.e., it must have a length
property). This ensures that you can safely access the length
property of the argument.
interface Lengthy {
length: number;
}
function logLength<T extends Lengthy>(obj: T): void {
console.log(obj.length);
}
logLength("hello"); // Output: 5
logLength([1, 2, 3]); // Output: 3
//logLength(123); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthy'.
Real-Life Use Case
Consider a function that fetches data from an API. Using generics, you can create a reusable function that can handle different types of data responses, maintaining type safety for each response type. For example, you might fetch user data, product data, or configuration data, each with different properties. Generics allow you to define a single fetch function that adapts to the specific data structure of the response.
Best Practices
T
is common, use more descriptive names like DataType
or ItemType
when it improves readability.
Interview Tip
Be prepared to explain what generics are, why they are useful, and how they improve type safety and code reusability. Be able to write simple generic functions, interfaces, and classes. Also, be ready to discuss generic constraints and their purpose. Demonstrate an understanding of how generics contribute to writing more maintainable and scalable code.
When to Use Generics
Use generics when you want to write a function, interface, or class that can work with different types of data without sacrificing type safety. They are particularly useful when you have a component that needs to handle different types but performs the same operation regardless of the type. Avoid using generics when the type is known and fixed, as it adds unnecessary complexity.
Memory Footprint
Generics themselves don't typically add a significant memory footprint at runtime. TypeScript generics are primarily a compile-time feature used for type checking. The type information is generally erased during compilation in standard JavaScript (this is known as type erasure), so there's no additional runtime overhead directly associated with the generic type parameters. However, using generics correctly can lead to better code organization and potentially fewer errors, which can indirectly improve performance and reduce memory usage by avoiding runtime errors and inefficiencies.
Alternatives
Alternatives to generics include using the any
type, function overloading, and union types. Using any
sacrifices type safety, while function overloading can become verbose if you need to support many different types. Union types can be useful when you have a limited number of known types, but generics are more flexible and scalable for handling a wider range of types.
Pros
Cons
FAQ
-
What is the difference between using `any` and using generics?
Usingany
disables type checking, which can lead to runtime errors. Generics provide type safety while allowing you to write reusable code that can work with different types. Generics enforce type constraints at compile time, whileany
bypasses them altogether. -
Can I have multiple type parameters in a generic function?
Yes, you can define multiple type parameters in a generic function or interface. For example:function combine<T, U>(a: T, b: U): [T, U] { return [a, b]; }
. -
What happens if I don't specify the type parameter when calling a generic function?
TypeScript can often infer the type parameter based on the arguments passed to the function. If it can't infer the type, it will default toany
, which defeats the purpose of using generics for type safety.