
Ts Basic Generics in TypeScript
Generics in TypeScript allow you to create reusable and flexible components, functions, or classes that can work with various types while maintaining type safety. They provide a way to define the type of data a function, class, or interface will work with at runtime.
Why Use Generics?
Generics help:
- Avoid Repetition: Reuse code for different types.
- Improve Type Safety: Ensure the correct types are used without type assertions or
any
. - Enhance Flexibility: Make components adaptable to a variety of data types.
Defining Generics
1. Generic Function
Generics in functions allow the type to be determined when the function is called.
function identity<T>(value: T): T { return value;}// Usagelet numberIdentity = identity<number>(42); // T is numberlet stringIdentity = identity<string>("Hello"); // T is string
Here, <T>
is a placeholder for the type, which is replaced when the function is called.
2. Generic Class
Generics in classes make them adaptable to different types.
class Box<T> { private content: T; constructor(content: T) { this.content = content; } getContent(): T { return this.content; }}// Usagelet numberBox = new Box<number>(123);console.log(numberBox.getContent()); // 123let stringBox = new Box<string>("TypeScript");console.log(stringBox.getContent()); // "TypeScript"
3. Generic Interfaces
Generic interfaces define the structure of data while maintaining flexibility.
interface Pair<T, U> { first: T; second: U;}// Usagelet stringNumberPair: Pair<string, number> = { first: "Age", second: 30 };let numberBooleanPair: Pair<number, boolean> = { first: 1, second: true };
4. Generic Constraints
You can restrict the types that a generic can accept using constraints.
function logLength<T extends { length: number }>(item: T): void { console.log(item.length);}// UsagelogLength("Hello"); // 5logLength([1, 2, 3]); // 3// logLength(42); // Error: number doesn't have a `length` property
Built-In Generics in TypeScript
Array
let numbers: Array<number> = [1, 2, 3];let strings: string[] = ["a", "b", "c"];
Promise
let promise: Promise<string> = new Promise((resolve) => { resolve("Hello, World!");});
ReadonlyArray
let readonlyNumbers: ReadonlyArray<number> = [1, 2, 3];// readonlyNumbers.push(4); // Error: Cannot modify a readonly array
Combining Generics with Functions and Classes
Generics are particularly powerful when combined with complex structures.
Example: Generic Function in a Generic Class
class DataStore<T> { private data: T[] = []; addItem(item: T): void { this.data.push(item); } getItems(): T[] { return this.data; }}// Usagelet stringStore = new DataStore<string>();stringStore.addItem("TypeScript");stringStore.addItem("Generics");console.log(stringStore.getItems()); // ["TypeScript", "Generics"]let numberStore = new DataStore<number>();numberStore.addItem(1);numberStore.addItem(2);console.log(numberStore.getItems()); // [1, 2]
When to Use Generics
- When writing utility functions that can work with different types.
- While building reusable components (e.g., dropdowns, lists).
- When creating type-safe data structures (e.g., linked lists, stacks).
Best Practices
Use Descriptive Generic Names:
T
,U
,K
,V
for simple generics.- Use more descriptive names (e.g.,
Item
,Key
) for clarity in complex scenarios.
Avoid Overusing Generics:
- Only use them when type flexibility is necessary. Overusing generics can make code harder to read.
Leverage Constraints:
- Add constraints to avoid unexpected type errors.