Generics: A feature that allows creating reusable components (functions, classes, interfaces) that work with multiple types while maintaining type safety.
<T> (or any identifier) to define a generic type parameter.
// Generics in functions and classes
// Generic function
function identity<T>(value: T): T { return value; }
// Generic class
class Box<T> {
private content: T;
constructor(value: T) { this.content = value; }
getContent(): T { return this.content; }
}
// Using generic function
const numberValue = identity<number>(42);
const stringValue = identity<string>("Hello");
console.log(numberValue); // 42
console.log(stringValue); // Hello
// Using generic class
const numberBox = new Box<number>(100);
const stringBox = new Box<string>("TypeScript");
console.log(numberBox.getContent()); // 100
console.log(stringBox.getContent()); // TypeScript
Output:
42
Hello
100
TypeScript
Generic Constraints: Restrict the types that can be used with generics by requiring them to meet certain conditions.
T extends Constraint (e.g., T extends { length: number }).length on arrays or strings).
// Generic constraints
interface HasLength { length: number; }
function getLength<T extends HasLength>(item: T): number {
return item.length;
}
class Container<T extends { id: number }> {
private item: T;
constructor(item: T) { this.item = item; }
getId(): number { return this.item.id; }
}
// Using constrained generic function
console.log(getLength<string>("TypeScript")); // 10
console.log(getLength<number[]>([1, 2, 3])); // 3
// console.log(getLength<number>(42)); // Error: number has no length
// Using constrained generic class
const container = new Container<{ id: number, name: string }>({ id: 1, name: "Item" });
console.log(container.getId()); // 1
Output:
10
3
1
Multiple Generic Parameters: Define multiple type parameters (e.g., <T, U>) to handle different types in a single function or class.
<T, U, ...> in function or class declarations.
// Multiple generic parameters
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
class Pair<K extends string, V> {
private key: K;
private value: V;
constructor(key: K, value: V) {
this.key = key;
this.value = value;
}
getPair(): { key: K, value: V } {
return { key: this.key, value: this.value };
}
}
// Using multiple generics in function
const merged = merge<{ name: string }, { age: number }>(
{ name: "kristal" },
{ age: 30 }
);
console.log(merged); // { name: "kristal", age: 30 }
// Using multiple generics in class
const pair = new Pair<"id", number>("user1", 42);
console.log(pair.getPair()); // { key: "user1", value: 42 }
Output:
{ name: "kristal", age: 30 }
{ key: "user1", value: 42 }
Project Structure:
ts-generics/
├── src/
│ └── main.ts
├── tsconfig.json
└── package.json
main.ts:
// Comprehensive generics example
interface Identifiable { id: number; }
interface Describable { description: string; }
// Generic function with constraints
function processItem<T extends Identifiable>(item: T): string {
return `Item ID: ${item.id}`;
}
// Generic class with multiple parameters and constraints
class DataStore<K extends string, V extends Identifiable & Describable> {
private store: Map<K, V>;
constructor() { this.store = new Map<K, V>(); }
add(key: K, value: V): void { this.store.set(key, value); }
get(key: K): V | undefined { return this.store.get(key); }
describeAll(): string[] {
const descriptions: string[] = [];
for (const item of this.store.values()) {
descriptions.push(`${item.id}: ${item.description}`);
}
return descriptions;
}
}
// Generic function with multiple parameters
function combine<T extends Identifiable, U extends Describable>(
item1: T, item2: U
): T & U {
return { ...item1, ...item2 };
}
// Usage
const item1 = { id: 1, name: "Laptop" };
const item2 = { description: "High-performance device" };
console.log(processItem(item1)); // Item ID: 1
const store = new DataStore<string, { id: number; description: string }>();
store.add("item1", { id: 1, description: "Item One" });
store.add("item2", { id: 2, description: "Item Two" });
console.log(store.describeAll()); // ["1: Item One", "2: Item Two"]
const combined = combine(
{ id: 3 },
{ description: "Combined Item" }
);
console.log(combined); // { id: 3, description: "Combined Item" }
package.json:
{
"name": "ts-generics",
"version": "1.0.0",
"scripts": {
"start": "tsc && node dist/main.js",
"build": "tsc",
"watch": "tsc --watch"
},
"devDependencies": {
"typescript": "^5.6.2"
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Steps to Run:
npm install.npm run build.npm start.Output:
Item ID: 1
["1: Item One", "2: Item Two"]
{ id: 3, description: "Combined Item" }
Description:
processItem and DataStore use generics for type-safe operations.T extends Identifiable and V extends Identifiable & Describable ensure required properties.DataStore<K, V> and combine<T, U> handle multiple types.id and description are available when needed.any type inference.T extends object), reducing type safety.identity<number>(42)).T extends { length: number }) to ensure required properties.T extends Identifiable & Describable).K for keys, V for values).T, U for generics).tsc or linters (e.g., eslint) to adopt type errors early.