ProgramaciónDesarrollador Frontend/Backend

¿Cómo está estructurada y funciona la opción Strict Function Types en TypeScript? ¿Cómo afecta a la verificación de tipos de funciones con covarianza y contravarianza, y en qué casos la falta de coincidencia de las firmas llevará a un error de compilación?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la pregunta

Por defecto, TypeScript permite cierta "flexibilidad" al hacer coincidir las firmas de tipos de funciones, permitiendo que las funciones covariantes y contravariantes sean consideradas compatibles. A partir de TypeScript 2.6, se introdujo la opción strictFunctionTypes, que garantiza una verificación estricta de tipos de funciones y previene múltiples clases de errores, especialmente en grandes bases de código.

Problema

Sin verificación estricta, puede suceder que un manejador de función o callback acepte un tipo de parámetros más específico o adicional y esto pase desapercibido para el desarrollador. Esto lleva a errores en tiempo de ejecución relacionados con la covarianza de tipos de retorno y la contravarianza de argumentos.

Solución

La opción strictFunctionTypes introduce una contravarianza estricta para los tipos de parámetros de funciones. Ahora, las funciones son compatibles solo si el tipo de parámetro fuente es un supertipo del parámetro de destino, y no al revés.

Ejemplo de código:

type Animal = { name: string }; type Cat = { name: string; meow: () => void }; let animalHandler: (a: Animal) => void; let catHandler: (c: Cat) => void; animalHandler = catHandler; // Error con strictFunctionTypes: el argumento es demasiado específico catHandler = animalHandler; // Permitido, Cat es un subtipo de Animal

Características clave:

  • Los argumentos de las funciones se verifican en búsqueda de compatibilidad de "supertipos" (contravarianza)
  • Los valores de retorno se verifican en búsqueda de covarianza (se permiten subtipos)
  • La violación de las firmas conlleva errores de compilación

Preguntas con trampa.

¿Se podría asignar un manejador con un tipo de parámetro más específico antes de la llegada de strictFunctionTypes?

Sí, antes de activar strictFunctionTypes, TypeScript permitía asignar funciones más específicas en lugar de generales, lo que llevaba a problemas en tiempo de ejecución:

enum E { A, B } const f: (e: E) => void = (e: E.A) => {} // Sin strictFunctionTypes: permitido

¿Cómo afecta strictFunctionTypes a los callbacks con parámetros opcionales?

Si los parámetros de la función de callback hacen que ciertos parámetros sean opcionales, la verificación estricta no permitirá usar una función con menos parámetros obligatorios en un lugar donde se espera una función con un mayor número. Esto previene situaciones en las que el callback no recibe los datos necesarios.

¿Habrá problemas de compatibilidad al activar strictFunctionTypes en proyectos antiguos?

Sí, existe el riesgo de que surjan nuevos errores de compilación, ya que muchas funciones y manejadores podrían haberse asignado entre sí violando la contravarianza. Esto se encuentra más comúnmente en callbacks o al utilizar APIs de bibliotecas de terceros sin una tipificación estricta.

Errores típicos y antipatrón

  • Uso de tipificación obsoleta de callbacks sin verificación estricta de parámetros
  • Intentar asignar una función con un tipo de parámetro más específico/reducido a un manejador más general
  • Ignorar errores al activar strictFunctionTypes (comentar la opción en lugar de corregir los tipos)

Ejemplo de la vida real

Caso negativo

En un gran proyecto, los manejadores de eventos aceptan tipos más específicos (MouseEvent en lugar del general Event). Esto no se detecta hasta que se activa la opción estricta, llevando a errores en tiempo de ejecución con diferentes fuentes de eventos.

Pros:

  • Prototipado más rápido

Contras:

  • Errores en tiempo de ejecución debido a la incompatibilidad de tipos de eventos
  • Depuración compleja tras la expansión del código

Caso positivo

El proyecto utiliza strictFunctionTypes desde el principio. Al agregar nuevos manejadores, todas las discrepancias entre los tipos son detectadas automáticamente por el compilador. El código se vuelve más resistente a errores tipográficos y es más fácil de mantener.

Pros:

  • Fiabilidad
  • Seguridad en la transmisión de funciones y manejadores
  • Comportamiento predecible al refactorizar

Contras:

  • Requiere un diseño cuidadoso de las firmas
  • En algunos casos, es necesario escribir envolturas adicionales o sobrecargas para compatibilidad