Hasta ahora, nuestros tipos eran fijos. string es siempre string.
Pero, ¿y si queremos crear una función o un componente que funcione con cualquier tipo, pero sin perder la seguridad?
Ahí entran los Generics. Son como “parámetros” pero para Tipos.
El Problema: Duplicación o any
Imaginemos una función que devuelve lo que le pasas (clásica función identity).
// Opción 1: Duplicar código para cada tipo
function returnString(val: string): string { return val; }
function returnNumber(val: number): number { return val; }
// Opción 2: Usar 'any' (Perdemos el tipo)
function returnAny(val: any): any { return val; }
const resultado = returnAny("Hola");
// TS no sabe que 'resultado' es string, cree que es 'any'.
// resultado.toUpperCase(); // ❌ No autocompleta// Opción 1: Duplicar código para cada tipo
function returnString(val: string): string { return val; }
function returnNumber(val: number): number { return val; }
// Opción 2: Usar 'any' (Perdemos el tipo)
function returnAny(val: any): any { return val; }
const resultado = returnAny("Hola");
// TS no sabe que 'resultado' es string, cree que es 'any'.
// resultado.toUpperCase(); // ❌ No autocompletaLa Solución: Generics <T>
Podemos decirle a la función: “Oye, vamos a pasarte un tipo al que llamaremos T. Úsalo para tipar el argumento y el retorno”.
function identity<T>(val: T): T {
return val;
}
// Uso explícito (le pasamos el tipo)
const num = identity<number>(123); // num es number
// Inferencia (TS es listo y lo adivina)
const str = identity("Hola"); // str es "Hola" (literal) o stringfunction identity<T>(val: T): T {
return val;
}
// Uso explícito (le pasamos el tipo)
const num = identity<number>(123); // num es number
// Inferencia (TS es listo y lo adivina)
const str = identity("Hola"); // str es "Hola" (literal) o stringLa T es una convención (viene de Type), pero podemos llamarlo como queramos: <Data>, <Response>, <Props>.
Interfaces Genéricas
Esto es súper común en React o respuestas de API.
// Una caja que puede contener cualquier COSA
interface Caja<T> {
contenido: T;
etiqueta: string;
}
const cajaDeZapatos: Caja<string> = {
contenido: "Nike Air",
etiqueta: "Deportes"
};
const cajaDeRegalo: Caja<number> = {
contenido: 1000,
etiqueta: "Dinero"
};// Una caja que puede contener cualquier COSA
interface Caja<T> {
contenido: T;
etiqueta: string;
}
const cajaDeZapatos: Caja<string> = {
contenido: "Nike Air",
etiqueta: "Deportes"
};
const cajaDeRegalo: Caja<number> = {
contenido: 1000,
etiqueta: "Dinero"
};Ejemplo Real: useState
Si hemos usado React, ya hemos usado Generics sin saberlo.
// useState es una función Genérica: function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]
// Inferencia:
const [name, setName] = useState("Alice"); // TS infiere que T es string
// Explícito (útil cuando el valor inicial es null):
const [user, setUser] = useState<Usuario | null>(null);// useState es una función Genérica: function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]
// Inferencia:
const [name, setName] = useState("Alice"); // TS infiere que T es string
// Explícito (útil cuando el valor inicial es null):
const [user, setUser] = useState<Usuario | null>(null);Uniones vs Generics
Igual has pensado: “¿Y si uso string | number en vez de <T>?”
function echo(val: string | number): string | number { return val; }
const res = echo("Hola"); // TS cree que 'res' es 'string | number' ❌
// Perdiste la precisión: TS ya no sabe que es un string.
Con <T>, TS mantiene la conexión: entró string -> sale string.
¡Probémoslo!
Crea una función envolver que metas lo que metas, te lo devuelva dentro de un array.
Si metes 10, sale [10]. Si metes "hola", sale ["hola"].
En la Parte 7, subiremos el nivel. Veremos cómo restringir esos Generics (para que no valga “cualquier cosa”) y cómo darles valores por defecto.