Introducción a los genéricos
Los genéricos te permiten crear componentes reutilizables que funcionan con múltiples tipos manteniendo la seguridad.
// Sin genéricos - se pierde información de tipos
function identityAny(arg: any): any {
return arg;
}
// Con genéricos - se preserva la información
function identity<T>(arg: T): T {
return arg;
}
// Uso
const num = identity<number>(42); // explícito: number
const str = identity("hello"); // inferido: string
// Función flecha genérica
const identityArrow = <T>(arg: T): T => arg;
Interfaces y tipos genéricos
// Interface genérica
interface Box<T> {
value: T;
getValue(): T;
}
const numberBox: Box<number> = {
value: 42,
getValue() {
return this.value;
}
};
// Alias de tipo genérico
type Result<T> = {
success: boolean;
data: T;
error?: string;
};
type UserResult = Result<User>;
type NumberResult = Result<number>;
// Genérico con múltiples parámetros
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair: KeyValuePair<string, number> = {
key: "age",
value: 30
};
Restricciones genéricas
// Restricción con extends
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK: string tiene length
logLength([1, 2, 3]); // OK: array tiene length
// logLength(123); // Error: number no tiene length
// Restricción con 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" es una key
// getProperty(person, "email"); // Error: "email" no es una key
// Múltiples restricciones
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`;
}
Clases genéricas
// Clase genérica
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");
// Clase genérica con restricción
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);
}
}
Tipos utilitarios incorporados
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial - todas las propiedades opcionales
type PartialUser = Partial<User>;
const update: PartialUser = { name: "Alice" };
// Required - todas las propiedades requeridas
type RequiredUser = Required<PartialUser>;
// Readonly - todas las propiedades readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: "Alice", email: "a@b.com", age: 30 };
// user.name = "Bob"; // Error!
// Pick - selecciona propiedades específicas
type UserPreview = Pick<User, "id" | "name">;
// Omit - excluye propiedades específicas
type UserWithoutEmail = Omit<User, "email">;
// Record - crea un tipo con claves y valores
type UserRoles = Record<string, "admin" | "user" | "guest">;
const roles: UserRoles = {
alice: "admin",
bob: "user"
};
// Exclude - excluye tipos de una unión
type Status = "pending" | "active" | "completed" | "cancelled";
type ActiveStatus = Exclude<Status, "cancelled">; // "pending" | "active" | "completed"
// Extract - extrae tipos de una unión
type OnlyCompleted = Extract<Status, "completed" | "cancelled">; // "completed" | "cancelled"
// NonNullable - elimina null y undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string
// ReturnType - obtener el tipo de retorno de una función
function createUser() {
return { id: 1, name: "Alice" };
}
type NewUser = ReturnType<typeof createUser>; // { id: number; name: string }
// Parameters - obtener tipos de parámetros de una función
function greet(name: string, age: number): void {}
type GreetParams = Parameters<typeof greet>; // [string, number]
Tipos utilitarios personalizados
// Hacer opcionales propiedades específicas
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type UserWithOptionalEmail = PartialBy<User, "email">;
// Hacer requeridas propiedades específicas
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
// Partial profundo (recursivo)
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// Tipo nullable
type Nullable<T> = T | null;
// Tipo de elemento de array
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Elem = ArrayElement<string[]>; // string
// Desempaquetar Promise
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // string
Ejemplos prácticos
// Respuesta genérica de API
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()
};
}
// Uso
const userResponse = await fetchData<User>("/api/user");
const usersResponse = await fetchData<User[]>("/api/users");
// Props genéricas para componente React
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>
);
}
// Manejador genérico de eventos
type EventHandler<T> = (event: T) => void;
const handleClick: EventHandler<React.MouseEvent> = (e) => {
console.log(e.clientX, e.clientY);
};
Puntos clave
- • Los genéricos crean componentes reutilizables y seguros
- • Usa
extendspara restringir tipos genéricos - • Los tipos utilitarios transforman tipos existentes
- •
keyofobtiene las keys de un tipo como unión - • Los tipos condicionales permiten lógica a nivel de tipos
- • Crea utilidades personalizadas para patrones comunes