Introduction to Generics
Generics enable you to create reusable components that work with multiple types while maintaining type safety.
// Without generics - lose type information
function identityAny(arg: any): any {
return arg;
}
// With generics - preserve type information
function identity<T>(arg: T): T {
return arg;
}
// Usage
const num = identity<number>(42); // explicit: number
const str = identity("hello"); // inferred: string
// Generic arrow function
const identityArrow = <T>(arg: T): T => arg;
Generic Interfaces and Types
// Generic interface
interface Box<T> {
value: T;
getValue(): T;
}
const numberBox: Box<number> = {
value: 42,
getValue() {
return this.value;
}
};
// Generic type alias
type Result<T> = {
success: boolean;
data: T;
error?: string;
};
type UserResult = Result<User>;
type NumberResult = Result<number>;
// Generic with multiple type parameters
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair: KeyValuePair<string, number> = {
key: "age",
value: 30
};
Generic Constraints
// Constraint with extends
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
// logLength(123); // Error: number has no length
// Constraint with keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 30 };
getProperty(person, "name"); // OK: "name" is a key
// getProperty(person, "email"); // Error: "email" is not a key
// Multiple constraints
interface Named {
name: string;
}
interface Aged {
age: number;
}
function greet<T extends Named & Aged>(entity: T): string {
return `Hello ${entity.name}, you are ${entity.age} years old`;
}
Generic Classes
// Generic class
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.pop(); // 2
const stringStack = new Stack<string>();
stringStack.push("hello");
// Generic class with constraint
class Repository<T extends { id: number }> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
findById(id: number): T | undefined {
return this.items.find(item => item.id === id);
}
remove(id: number): void {
this.items = this.items.filter(item => item.id !== id);
}
}
Built-in Utility Types
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial - all properties optional
type PartialUser = Partial<User>;
const update: PartialUser = { name: "Alice" };
// Required - all properties required
type RequiredUser = Required<PartialUser>;
// Readonly - all properties readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: "Alice", email: "a@b.com", age: 30 };
// user.name = "Bob"; // Error!
// Pick - select specific properties
type UserPreview = Pick<User, "id" | "name">;
// Omit - exclude specific properties
type UserWithoutEmail = Omit<User, "email">;
// Record - create object type with keys and values
type UserRoles = Record<string, "admin" | "user" | "guest">;
const roles: UserRoles = {
alice: "admin",
bob: "user"
};
// Exclude - exclude types from union
type Status = "pending" | "active" | "completed" | "cancelled";
type ActiveStatus = Exclude<Status, "cancelled">; // "pending" | "active" | "completed"
// Extract - extract types from union
type OnlyCompleted = Extract<Status, "completed" | "cancelled">; // "completed" | "cancelled"
// NonNullable - remove null and undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string
// ReturnType - get function return type
function createUser() {
return { id: 1, name: "Alice" };
}
type NewUser = ReturnType<typeof createUser>; // { id: number; name: string }
// Parameters - get function parameter types
function greet(name: string, age: number): void {}
type GreetParams = Parameters<typeof greet>; // [string, number]
Custom Utility Types
// Make specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type UserWithOptionalEmail = PartialBy<User, "email">;
// Make specific properties required
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
// Deep partial (recursive)
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// Nullable type
type Nullable<T> = T | null;
// Array element type
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Elem = ArrayElement<string[]>; // string
// Promise unwrap
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // string
Practical Examples
// Generic API response
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
const data = await response.json();
return {
data,
status: response.status,
message: "Success",
timestamp: new Date()
};
}
// Usage
const userResponse = await fetchData<User>("/api/user");
const usersResponse = await fetchData<User[]>("/api/users");
// Generic React component props
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Generic event handler
type EventHandler<T> = (event: T) => void;
const handleClick: EventHandler<React.MouseEvent> = (e) => {
console.log(e.clientX, e.clientY);
};
Key Takeaways
- • Generics create reusable, type-safe components
- • Use
extendsto constrain generic types - • Utility types transform existing types
- •
keyofgets keys of a type as union - • Conditional types enable type-level logic
- • Create custom utility types for common patterns