
Funciones genéricas básicas
Cuando el tipo de salida de una función depende del tipo de entrada, o cuando dos parámetros deben compartir el mismo tipo, los tipos concretos resultan insuficientes. Las funciones genéricas resuelven este problema introduciendo parámetros de tipo que actuan como variables para los tipos.
Un parámetro de tipo se declara entre angulos <T> justo antes de la lista de parámetros:
function primerElemento<T>(array: T[]): T | undefined {
return array[0]
}
const número = primerElemento([10, 20, 30])
console.log(número) // 10 - TypeScript infiere T como number
const texto = primerElemento(["hola", "mundo"])
console.log(texto) // "hola" - TypeScript infiere T como string
const vacío = primerElemento([])
console.log(vacío) // undefined
TypeScript infiere automáticamente el parámetro de tipo a partir de los argumentos. Solo es necesario especificarlo explícitamente cuando la inferencia no es suficiente.
Múltiples parámetros de tipo
Una función genérica puede declarar varios parámetros de tipo para relacionar distintos valores:
function emparejar<T, U>(primero: T, segundo: U): [T, U] {
return [primero, segundo]
}
const par1 = emparejar("nombre", 42)
console.log(par1) // ["nombre", 42] - tipo [string, number]
const par2 = emparejar(true, [1, 2, 3])
console.log(par2) // [true, [1, 2, 3]] - tipo [boolean, number[]]
Funciones genéricas con arrays
Los genéricos son especialmente útiles para funciones de utilidad que operan sobre arrays de cualquier tipo:
function mapear<T, U>(array: T[], transformar: (elemento: T) => U): U[] {
const resultado: U[] = []
for (const elemento of array) {
resultado.push(transformar(elemento))
}
return resultado
}
const números = [1, 2, 3, 4]
const textos = mapear(números, (n) => `Número: ${n}`)
console.log(textos) // ["Número: 1", "Número: 2", "Número: 3", "Número: 4"]
const longitudes = mapear(["hola", "mundo", "ts"], (s) => s.length)
console.log(longitudes) // [4, 5, 2]
Restricciones básicas con extends
Se puede limitar que tipos acepta un parámetro genérico mediante la cláusula extends:
function obtenerLongitud<T extends { length: number }>(elemento: T): number {
return elemento.length
}
console.log(obtenerLongitud("TypeScript")) // 10
console.log(obtenerLongitud([1, 2, 3])) // 3
console.log(obtenerLongitud({ length: 5 })) // 5
// Error: Argument of type 'number' is not assignable to parameter of type '{ length: number }'
// obtenerLongitud(42)
La restricción extends garantiza que el tipo genérico tenga las propiedades necesarias para que la función opere correctamente:
interface ConNombre {
nombre: string
}
function obtenerNombre<T extends ConNombre>(objeto: T): string {
return objeto.nombre
}
const usuario = { nombre: "Elena", edad: 28, rol: "admin" }
const producto = { nombre: "Laptop", precio: 999 }
console.log(obtenerNombre(usuario)) // "Elena"
console.log(obtenerNombre(producto)) // "Laptop"
Funciones arrow genéricas
Las funciones arrow también pueden ser genéricas:
const filtrar = <T>(array: T[], predicado: (item: T) => boolean): T[] => {
return array.filter(predicado)
}
const mayoresQueCinco = filtrar([1, 8, 3, 10, 2], (n) => n > 5)
console.log(mayoresQueCinco) // [8, 10]
const largos = filtrar(["a", "hola", "hi", "typescript"], (s) => s.length > 2)
console.log(largos) // ["hola", "typescript"]
Call signatures y construct signatures
Call signatures
Una call signature define una función invocable dentro de un tipo de objeto. A diferencia de las expresiones de tipo función, las call signatures usan : en lugar de => entre los parámetros y el tipo de retorno. Esto permite que el tipo de objeto combine propiedades y capacidad de invocación:
type Formateador = {
(valor: number): string
precision: number
moneda: string
}
function crearFormateador(precision: number, moneda: string): Formateador {
const fn = function(valor: number): string {
return `${valor.toFixed(fn.precision)} ${fn.moneda}`
} as Formateador
fn.precision = precision
fn.moneda = moneda
return fn
}
const formatoEUR = crearFormateador(2, "EUR")
console.log(formatoEUR(19.999)) // "20.00 EUR"
console.log(formatoEUR.precision) // 2
console.log(formatoEUR.moneda) // "EUR"
Las call signatures son necesarias cuando un valor es invocable y además tiene propiedades. Las expresiones de tipo función (
(a: number) => string) no permiten declarar propiedades adicionales.
Las call signatures también pueden definirse con interfaces:
interface Validador {
(valor: string): boolean
nombre: string
descripcion: string
}
function crearValidador(nombre: string, descripcion: string, regla: (v: string) => boolean): Validador {
const validador = regla as Validador
validador.nombre = nombre
validador.descripcion = descripcion
return validador
}
const validarEmail = crearValidador(
"email",
"Valida formato de email",
(v) => v.includes("@") && v.includes(".")
)
console.log(validarEmail("user@mail.com")) // true
console.log(validarEmail.nombre) // "email"
console.log(validarEmail.descripcion) // "Valida formato de email"
Construct signatures
Una construct signature describe un tipo que puede invocarse con el operador new. Se define anteponiendo la palabra clave new a una call signature:
type Constructor = {
new (nombre: string, edad: number): { nombre: string; edad: number }
}
function crearInstancia(ctor: Constructor, nombre: string, edad: number) {
return new ctor(nombre, edad)
}
class Persona {
constructor(public nombre: string, public edad: number) {}
}
const persona = crearInstancia(Persona, "Carlos", 30)
console.log(persona.nombre) // "Carlos"
console.log(persona.edad) // 30
Algunos objetos en JavaScript pueden invocarse tanto con new como sin el. Un tipo puede combinar ambas firmas:
interface CreadorFecha {
new (valor: number): Date
(valor: string): string
}
Call signatures genéricas
Las call signatures también admiten parámetros de tipo, lo que permite crear tipos de función genéricos dentro de objetos:
interface Transformador {
<T, U>(entrada: T, convertir: (valor: T) => U): U
}
const transformar: Transformador = (entrada, convertir) => {
return convertir(entrada)
}
const longitud = transformar("TypeScript", (s) => s.length)
console.log(longitud) // 10
const duplicado = transformar(5, (n) => n * 2)
console.log(duplicado) // 10
Tipado del parámetro this
En JavaScript, el valor de this dentro de una función depende del contexto de invocación. TypeScript permite anotar el tipo de this como un parámetro especial que no cuenta como argumento real de la función.
El parámetro this debe ser el primer parámetro y no afecta a la forma en que se invoca la función:
interface Boton {
etiqueta: string
activo: boolean
onClick(this: Boton): void
}
const boton: Boton = {
etiqueta: "Enviar",
activo: true,
onClick() {
console.log(`Boton "${this.etiqueta}" pulsado - Activo: ${this.activo}`)
}
}
boton.onClick() // "Boton "Enviar" pulsado - Activo: true"
El parámetro
thises una característica de TypeScript que desaparece en el JavaScript compilado. Su único propósito es que el compilador verifique el tipo del contextothis.
this en callbacks
El tipado de this es especialmente útil en APIs basadas en callbacks donde otro objeto controla la invocación:
interface Evento {
tipo: string
timestamp: number
}
interface Manejador {
nombre: string
manejar(this: Manejador, evento: Evento): void
}
const manejador: Manejador = {
nombre: "LogHandler",
manejar(evento) {
console.log(`[${this.nombre}] Evento: ${evento.tipo} a las ${evento.timestamp}`)
}
}
manejador.manejar({ tipo: "click", timestamp: Date.now() })
this con funciones arrow
Las funciones arrow no tienen su propio this: heredan el this del ámbito que las contiene. Por está razon, no es posible anotar el parámetro this en una función arrow:
interface Temporizador {
duracion: number
mensaje: string
iniciar(): void
}
const temporizador: Temporizador = {
duracion: 1000,
mensaje: "Tiempo agotado",
iniciar() {
// La función arrow hereda this del método iniciar
setTimeout(() => {
console.log(`${this.mensaje} (${this.duracion}ms)`)
}, this.duracion)
}
}
temporizador.iniciar()
Tipado contextual en callbacks
El tipado contextual es la capacidad de TypeScript de inferir los tipos de los parámetros de una función a partir del contexto donde se utiliza. Cuando una función se pasa como argumento a otra función cuya firma ya está definida, no es necesario anotar los tipos de los parámetros del callback.
const números = [1, 2, 3, 4, 5]
// TypeScript infiere que 'n' es number porque números es number[]
const dobles = números.map((n) => n * 2)
console.log(dobles) // [2, 4, 6, 8, 10]
// TypeScript infiere 'elemento' como string y 'indice' como number
const frutas = ["manzana", "pera", "naranja"]
frutas.forEach((elemento, indice) => {
console.log(`${indice}: ${elemento}`)
})
Tipado contextual con tipos de función propios
El tipado contextual funciona con cualquier tipo de función, no solo con los métodos de la biblioteca estándar:
type Procesador = (entrada: string, posición: number) => string
function aplicarProcesador(datos: string[], procesador: Procesador): string[] {
return datos.map((item, i) => procesador(item, i))
}
// No es necesario anotar los parámetros del callback
const resultado = aplicarProcesador(
["hola", "mundo", "typescript"],
(texto, pos) => `[${pos}] ${texto.toUpperCase()}`
)
console.log(resultado) // ["[0] HOLA", "[1] MUNDO", "[2] TYPESCRIPT"]
Tipado contextual y parámetros no utilizados
Cuando un callback no necesita todos los parámetros que el tipo define, se pueden omitir los parámetros finales. TypeScript permite que funciones con menos parámetros ocupen el lugar de funciones con más parámetros:
type ManejadorEvento = (tipo: string, datos: unknown, timestamp: number) => void
// Solo usa el primer parámetro
const logSimple: ManejadorEvento = (tipo) => {
console.log(`Evento: ${tipo}`)
}
// Usa dos parámetros
const logConDatos: ManejadorEvento = (tipo, datos) => {
console.log(`Evento: ${tipo}`, datos)
}
// Usa todos los parámetros
const logCompleto: ManejadorEvento = (tipo, datos, timestamp) => {
console.log(`[${timestamp}] Evento: ${tipo}`, datos)
}
logSimple("click", {}, Date.now())
logConDatos("submit", { campo: "email" }, Date.now())
logCompleto("error", { código: 500 }, Date.now())
Tipado contextual en métodos de array
Los métodos de arrays como filter, reduce y sort se benefician del tipado contextual:
interface Producto {
nombre: string
precio: number
stock: number
}
const productos: Producto[] = [
{ nombre: "Teclado", precio: 75, stock: 10 },
{ nombre: "Raton", precio: 25, stock: 0 },
{ nombre: "Monitor", precio: 350, stock: 5 },
{ nombre: "Auriculares", precio: 120, stock: 3 }
]
// TypeScript infiere 'p' como Producto en cada callback
const disponibles = productos.filter((p) => p.stock > 0)
const nombres = productos.map((p) => p.nombre)
const precioTotal = productos.reduce((total, p) => total + p.precio * p.stock, 0)
const ordenadosPorPrecio = [...productos].sort((a, b) => a.precio - b.precio)
console.log(disponibles.length) // 3
console.log(nombres) // ["Teclado", "Raton", "Monitor", "Auriculares"]
console.log(precioTotal) // 3110
console.log(ordenadosPorPrecio[0].nombre) // "Raton"
Contexto en funciones asignadas a variables tipadas
El tipado contextual también se aplica cuando se asigna una función a una variable cuyo tipo ya está declarado:
type Comparador<T> = (a: T, b: T) => number
const compararPorNombre: Comparador<Producto> = (a, b) => {
return a.nombre.localeCompare(b.nombre)
}
const compararPorPrecio: Comparador<Producto> = (a, b) => {
return a.precio - b.precio
}
const productosOrdenados = [...productos].sort(compararPorNombre)
console.log(productosOrdenados.map((p) => p.nombre))
// ["Auriculares", "Monitor", "Raton", "Teclado"]
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Crear funciones genéricas básicas con parámetros de tipo. Definir call signatures y construct signatures en tipos de objeto. Anotar el parámetro this en funciones. Aplicar tipado contextual en callbacks.