Mucha gente usa TypeScript sin haber escrito nunca un generic. El código compila, funciona, y parece suficiente. Cuando necesitas una función que funciona con cualquier tipo de dato manteniendo type safety, los generics dejan de ser teoría y se vuelven necesidad.
Generics básicos
Un generic es un parámetro de tipo. Igual que una función acepta parámetros de valor, un generic acepta parámetros de tipo. Escribe una vez, tipa para cualquier tipo.
// Sin generic: necesitas una función para cada tipo
function getFirstString(arr: string[]): string | undefined { return arr[0] }
function getFirstNumber(arr: number[]): number | undefined { return arr[0] }
// Con generic: una función para todos
function getFirst<T>(arr: T[]): T | undefined {
return arr[0]
}
const a = getFirst(['a', 'b', 'c']) // tipo: string | undefined
const b = getFirst([1, 2, 3]) // tipo: number | undefined
TypeScript infiere el tipo del generic automáticamente. Cuando pasas un array de strings, T se convierte en string. Sin necesidad de declararlo.
Constraints
A veces el generic necesita una garantía. Los constraints resuelven esto con extends:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Marc', age: 30 }
getProperty(user, 'name') // ok: devuelve string
getProperty(user, 'email') // error: 'email' no existe en user
keyof T devuelve un union de los nombres de las propiedades de T. K extends keyof T garantiza que la clave pasada existe en el objeto. El error ocurre en tiempo de compilación, no en runtime.
Conditional types
Los conditional types usan la sintaxis T extends U ? X : Y. El tipo resultante depende de una condición. Piensa en un ternario, pero para tipos.
type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> // true
type B = IsString<42> // false
Caso de uso real: derivar tipos a partir de inputs. Una API que retorna tipos diferentes según el parámetro.
type ApiResponse<T extends 'user' | 'product'> =
T extends 'user' ? { id: string; name: string; email: string }
: T extends 'product' ? { id: string; title: string; price: number }
: never
type UserResponse = ApiResponse<'user'> // { id: string; name: string; email: string }
type ProductResponse = ApiResponse<'product'> // { id: string; title: string; price: number }
Mapped types
Los mapped types iteran sobre las propiedades de un tipo y producen un nuevo tipo. La sintaxis es { [K in Keys]: NewType }.
// Hace todas las propiedades opcionales
type MyPartial<T> = { [K in keyof T]?: T[K] }
// Hace todas las propiedades readonly
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }
// Hace propiedades específicas obligatorias
type WithRequired<T, K extends keyof T> =
T & Required<Pick<T, K>>
type User = { name?: string; email?: string; id?: string }
type UserWithId = WithRequired<User, 'id'>
// resultado: { name?: string; email?: string; id: string }
Utility types de TypeScript
TypeScript ya viene con utility types listos. Conocer los más usados evita reinventar la rueda:
type User = { id: string; name: string; email: string; role: 'admin' | 'user' }
// Pick: selecciona propiedades
type UserBasic = Pick<User, 'id' | 'name'>
// Omit: elimina propiedades
type CreateUser = Omit<User, 'id'>
// Record: crea un tipo con claves y valores tipados
type Roles = Record<string, { permissions: string[] }>
// Extract: extrae tipos de un union
type AdminRole = Extract<User['role'], 'admin'>
// Exclude: elimina tipos de un union
type NonAdmin = Exclude<User['role'], 'admin'> // 'user'
Patrón real: tipar una función que acepta callbacks
function mapWithFallback<TInput, TOutput>(
items: TInput[],
transform: (item: TInput) => TOutput,
fallback: TOutput
): TOutput[] {
return items.map(item => {
try {
return transform(item)
} catch {
return fallback
}
})
}
// TypeScript infiere TInput = string, TOutput = number
const result = mapWithFallback(
['1', '2', 'abc'],
s => parseInt(s),
0
) // tipo: number[]
Cuándo dejar de complicar
Los generics y conditional types son poderosos, pero tienen un costo de legibilidad. Si estás escribiendo un tipo que nadie del equipo puede leer en 10 segundos, simplifica. Type safety importa, pero el mantenimiento importa más.
Usa generics cuando necesitas flexibilidad de tipo. Usa conditional types cuando tienes lógica de tipos genuina. Usa mapped types cuando necesitas transformar estructuras. Cuando nada de eso aplica, un any localizado y documentado es mejor que un tipo genial que nadie entiende.
¿Te gustó el contenido?
Construyo productos web y soluciones con IA de la manera correcta — arquitectura sólida, código sostenible y entrega real.
Hablemos