Funciones con tipado avanzado

Intermedio
TypeScript
TypeScript
Actualizado: 04/05/2026

Diagrama: tutorial-typescript-funciones-tipado-avanzado

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 this es una característica de TypeScript que desaparece en el JavaScript compilado. Su único propósito es que el compilador verifique el tipo del contexto this.

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 - Autor del tutorial

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.