TypeScript: Funciones, parámetros y generadores

TypeScript: Funciones, parámetros y generadores

Tabla de Contenido

Note

Cuarto artículo de la serie sobre TypeScript. Cubriremos el tipado de funciones: parámetros, tipos de retorno, parámetros opcionales y por defecto, el operador resto, call signatures, generadores e iteradores.

Las funciones son el núcleo de cualquier base de código, y es justo donde el tipado aporta más valor: una firma bien tipada documenta el contrato de entrada y salida, y convierte cualquier llamada inválida en un error de compilación. Analicemos cómo TypeScript modela las funciones en toda su variedad.

Declaración y tipado de funciones

Una función tipada declara el tipo de cada parámetro y, opcionalmente, el tipo de retorno:

function add(a: number, b: number): number {
  return a + b
}

TypeScript admite todas las formas de definir funciones de JavaScript, y a todas les aplica el mismo sistema de tipos:

// Named function
function greet(name: string) {
  return 'hello ' + name
}

// Function expression
let greet2 = function (name: string) {
  return 'hello ' + name
}

// Arrow function
let greet3 = (name: string) => {
  return 'hello ' + name
}

// Shorthand arrow function
let greet4 = (name: string) => 'hello ' + name

El tipado de los parámetros intercepta llamadas inválidas en el sitio exacto del error:

add(1)            // Error TS2554: se esperaban 2 argumentos, se recibió 1.
add(1, 'a')       // Error TS2345: 'string' no es asignable a 'number'.

Parámetros opcionales y por defecto

Un parámetro opcional se marca con ? y debe gestionarse dentro del cuerpo, ya que puede llegar como undefined:

function log(message: string, userId?: string) {
  let time = new Date().toLocaleTimeString()
  console.log(time, message, userId || 'Not signed in')
}

log('Page loaded')
log('User signed in', 'da763be')

Un parámetro con valor por defecto se vuelve opcional automáticamente, y TypeScript infiere su tipo a partir del valor asignado:

function log(message: string, userId = 'Not signed in') {
  let time = new Date().toISOString()
  console.log(time, message, userId)
}

Warning

Los parámetros opcionales deben ubicarse después de todos los obligatorios, y no pueden combinarse con el operador resto.

También puedes apoyarte en un type para estructurar parámetros de configuración:

type Context = {
  appId?: string
  userId?: string
}

function log(message: string, context: Context = {}) {
  let time = new Date().toISOString()
  console.log(time, message, context.userId)
}

Operador resto: funciones variádicas

El operador resto captura un número dinámico de argumentos en un arreglo tipado, lo que produce firmas más legibles que recibir un arreglo explícito:

// Recibe un arreglo
function sum(numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0)
}
sum([1, 2, 3]) // 6

// Variádica con operador resto
function sumVariadicSafe(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0)
}
sumVariadicSafe(1, 2, 3) // 6

call, apply y bind

TypeScript respeta los mecanismos clásicos de invocación de JavaScript, aplicando la verificación de tipos a sus argumentos:

function add(a: number, b: number): number {
  return a + b
}

add(10, 20)                // 30
add.apply(null, [10, 20])  // 30
add.call(null, 10, 20)     // 30
add.bind(null, 10, 20)()   // 30

Generadores

Los generadores producen valores bajo demanda. Se declaran con un asterisco tras function y emiten valores con yield:

function* createFibonacciGenerator() {
  let a = 0
  let b = 1
  while (true) {
    yield a
    ;[a, b] = [b, a + b]
  }
}

let fib = createFibonacciGenerator() // IterableIterator<number>
fib.next() // { value: 0, done: false }
fib.next() // { value: 1, done: false }
fib.next() // { value: 1, done: false }
fib.next() // { value: 2, done: false }

El método next() extrae el siguiente valor de la secuencia. TypeScript infiere el tipo IterableIterator<number> a partir de lo que produce yield.

Iteradores

Un iterador se define implementando el método [Symbol.iterator]. TypeScript es capaz de reconocer la estructura y tiparla correctamente:

let numbers = {
  *[Symbol.iterator]() {
    for (let n = 1; n <= 10; n++) {
      yield n
    }
  }
}

Call signatures

Una call signature describe el tipo de una función de forma independiente a su implementación. Para la siguiente función:

function sum(a: number, b: number): number {
  return a + b
}

su firma de llamada se expresa así:

(a: number, b: number) => number

El tipo genérico Function es un comodín que no aporta información sobre la firma concreta; las call signatures permiten tipar funciones con precisión. Además, cuando una función puede retornar varios tipos, cada uno debe tratarse para satisfacer al typechecker:

function area(radius: number): number | null {
  if (radius < 0) {
    return null
  }
  return Math.PI * radius ** 2
}

let a = area(3)
if (a !== null) {
  console.info('result:', a)   // dentro del guard, a es number
}

Conclusión

El tipado de funciones convierte cada firma en un contrato verificable: parámetros opcionales y por defecto modelan flexibilidad controlada, el operador resto expresa funciones variádicas con claridad, las call signatures tipan funciones como ciudadanos de primera clase, y los generadores e iteradores se integran sin fricción en el sistema de tipos.

En el siguiente artículo abordaremos las interfaces, el mecanismo de TypeScript para definir contratos extensibles.

Etiquetas :