Aquí es donde mucha gente se pierde. Empezamos a ver <T>, interface, type, return T… todo mezclado y parece jeroglífico.
Vamos a desenredarlo paso a paso.
Generics en Interfaces vs Tipos
¿Cuándo usar uno u otro? La sintaxis es casi idéntica.
// Opción A: Interface Genérica
interface CajaInterface<T> {
valor: T;
}
// Opción B: Type Genérico
type CajaType<T> = {
valor: T;
};
// Se usan IGUAL
const a: CajaInterface<string> = { valor: "Hola" };
const b: CajaType<string> = { valor: "Hola" };// Opción A: Interface Genérica
interface CajaInterface<T> {
valor: T;
}
// Opción B: Type Genérico
type CajaType<T> = {
valor: T;
};
// Se usan IGUAL
const a: CajaInterface<string> = { valor: "Hola" };
const b: CajaType<string> = { valor: "Hola" };No hay diferencia funcional real para datos simples. Usemos la que prefiramos (solemos usar interface para objetos y type para funciones/uniones).
El Patrón “Wrapper” (Datawrapper)
Este es el caso de uso #1 en el mundo real: Respuestas de API.
Tenemos una estructura fija (data, status, error) pero el contenido de data cambia según la llamada.
// Definamos la "cáscara" genérica
interface ApiResponse<Data> {
status: number;
message: string;
data: Data; // 👈 Aquí va la magia
}
// Definamos nuestros modelos concretos
interface User { id: number; name: string; }
interface Product { sku: string; price: number; }
// ¡Combinémoslos!
type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;
function fetchUser(): UserResponse {
return {
status: 200,
message: "OK",
data: { id: 1, name: "BartoloDev" } // TS sabe que data es User
};
}// Definamos la "cáscara" genérica
interface ApiResponse<Data> {
status: number;
message: string;
data: Data; // 👈 Aquí va la magia
}
// Definamos nuestros modelos concretos
interface User { id: number; name: string; }
interface Product { sku: string; price: number; }
// ¡Combinémoslos!
type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;
function fetchUser(): UserResponse {
return {
status: 200,
message: "OK",
data: { id: 1, name: "BartoloDev" } // TS sabe que data es User
};
}”Prop Drilling” de Tipos
A veces tenemos Generics dentro de Generics. Es como pasar una variable de un abuelo a un nieto.
interface PaginatedResponse<T> {
items: T[]; // Array de T
total: number;
page: number;
}
// ApiResponse contiene PaginatedResponse, que contiene User
type UsersApi = ApiResponse<PaginatedResponse<User>>;
// Parece complejo, pero TS lo resuelve solo:
// UsersApi = {
// status: number,
// message: string,
// data: {
// items: User[],
// total: number,
// page: number,
// }
// }interface PaginatedResponse<T> {
items: T[]; // Array de T
total: number;
page: number;
}
// ApiResponse contiene PaginatedResponse, que contiene User
type UsersApi = ApiResponse<PaginatedResponse<User>>;
// Parece complejo, pero TS lo resuelve solo:
// UsersApi = {
// status: number,
// message: string,
// data: {
// items: User[],
// total: number,
// page: number,
// }
// }Generics en Funciones Flecha
La sintaxis aquí puede llegar a asustar, especialmente en archivos .tsx (React) y donde se vuelve más complejo.
// Función normal
function identity<T>(arg: T): T { return arg; }
// Función flecha
const identityArrow = <T>(arg: T): T => arg;
// ⚠️ En archivos .tsx (React), necesitamos una coma para que no crea que es HTML
const hookEnReact = <T,>(arg: T) => { ... };// Función normal
function identity<T>(arg: T): T { return arg; }
// Función flecha
const identityArrow = <T>(arg: T): T => arg;
// ⚠️ En archivos .tsx (React), necesitamos una coma para que no crea que es HTML
const hookEnReact = <T,>(arg: T) => { ... };¡Probémoslo!
Creemos una interfaz Respuesta<T> con datos: T y error: string | null.
Luego creemos un objeto que use esa interfaz con number.
En la Parte 9, vamos a relajarnos un poco y ver los Utility Types (Partial, Pick, Omit). Son tipos genéricos que TS ya trae de serie para hacernos la vida más fácil.