En la parte anterior vimos que <T> puede ser cualquier cosa. A veces, eso es demasiado flexible.
¿Qué pasa si queremos que T tenga sí o sí una propiedad .length?
Restricciones (extends)
Imaginemos esta función:
function obtenerLongitud<T>(item: T) {
return item.length; // ❌ Error: Property 'length' does not exist on type 'T'.
}
// TS se queja porque T podría ser un número (que no tiene .length),
// o un boolean...function obtenerLongitud<T>(item: T) {
return item.length; // ❌ Error: Property 'length' does not exist on type 'T'.
}
// TS se queja porque T podría ser un número (que no tiene .length),
// o un boolean...Para arreglarlo, usamos extends para poner una restricción (Constraint).
// Le decimos: "T puede ser lo que quieras, PERO debe extender (cumplir) esta forma"
interface TieneLongitud {
length: number;
}
function obtenerLongitud<T extends TieneLongitud>(item: T) {
return item.length; // ✅ Ahora TS sabe que existe
}
obtenerLongitud("Hola"); // ✅ string tiene .length
obtenerLongitud([1, 2, 3]); // ✅ array tiene .length
obtenerLongitud(123); // ❌ Error: number no tiene .length// Le decimos: "T puede ser lo que quieras, PERO debe extender (cumplir) esta forma"
interface TieneLongitud {
length: number;
}
function obtenerLongitud<T extends TieneLongitud>(item: T) {
return item.length; // ✅ Ahora TS sabe que existe
}
obtenerLongitud("Hola"); // ✅ string tiene .length
obtenerLongitud([1, 2, 3]); // ✅ array tiene .length
obtenerLongitud(123); // ❌ Error: number no tiene .lengthGenerics con keyof (El truco maestro)
Este patrón lo veremos en todas partes. Queremos una función que obtenga una propiedad de un objeto de forma segura.
Necesitamos definir dos Generics:
T: El objeto.K: La clave (key). PeroKno puede ser cualquier string, debe ser una key de T.
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const developer = {
name: "BartoloDev",
language: "TypeScript"
};
getProperty(developer, "name"); // ✅ TS sabe que esto devuelve string
getProperty(developer, "age"); // ❌ Error: "age" no es una key de developerfunction getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const developer = {
name: "BartoloDev",
language: "TypeScript"
};
getProperty(developer, "name"); // ✅ TS sabe que esto devuelve string
getProperty(developer, "age"); // ❌ Error: "age" no es una key de developerValores por Defecto (=)
Al igual que en las funciones normales (function suma(a, b = 0)), los Generics pueden tener valores por defecto.
Esto es muy útil para no obligar al usuario a escribir el tipo siempre.
// Si no pasamos T, asumiré que es 'string'
interface RespuestaAPI<T = string> {
data: T;
error: boolean;
}
// Uso sin pasar tipo (usa el default)
const respuestaSimple: RespuestaAPI = {
data: "Todo ok", // T es string
error: false
};
// Uso especificando tipo
const respuestaCompleja: RespuestaAPI<number> = {
data: 200, // T es number
error: false
};// Si no pasamos T, asumiré que es 'string'
interface RespuestaAPI<T = string> {
data: T;
error: boolean;
}
// Uso sin pasar tipo (usa el default)
const respuestaSimple: RespuestaAPI = {
data: "Todo ok", // T es string
error: false
};
// Uso especificando tipo
const respuestaCompleja: RespuestaAPI<number> = {
data: 200, // T es number
error: false
};¡Probémoslo!
Creemos una función fusionar que tome dos objetos obj1 y obj2 y los combine. Tipemos la función usando dos generics T y U.
En la Parte 8, abordaremos una de las mayores confusiones: Generics en Interfaces vs en Tipos, y cómo pasar tipos de padres a hijos (prop drilling de tipos).