ProgramaciónDesarrollador Frontend/Fullstack

¿Cómo funciona el tipo ReturnType<T> en TypeScript, en qué se diferencia de la inferencia manual del tipo de valor de retorno de una función y cuáles son los riesgos/beneficios de su uso?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la pregunta

Con el desarrollo de TypeScript, surgió la necesidad de extraer automáticamente el tipo de valor de retorno de una función, especialmente para proyectos grandes con muchas funciones interconectadas. Para esto se introdujo el tipo utilitario ReturnType<T>, que apareció en la biblioteca estándar a partir de la versión 2.8 de TypeScript.

Problema

En proyectos grandes y complejos, a veces es difícil mantener la actualización de los tipos; si se indica manualmente el tipo de retorno de cada función y se produce un cambio en las firmas, es fácil obtener inconsistencias cuando las firmas y las implementaciones no coinciden. Además, si una función devuelve una estructura compleja, no siempre es fácil sincronizar manualmente la descripción del tipo de retorno en todos los lugares de uso.

Solución

El tipo utilitario ReturnType<T> extrae automáticamente el tipo de retorno de las funciones y puede utilizarse para tipar el resultado de la llamada a la función en cualquier parte del código. Esto reduce la cantidad de mantenimiento manual de la infraestructura de tipos y minimiza los errores relacionados con la discrepancia entre el tipo descrito y el tipo real del valor de retorno.

Ejemplo de código:

function createUser(name: string, age: number) { return { name, age, created: new Date() }; } type User = ReturnType<typeof createUser>; // User: { name: string; age: number; created: Date; }

Características clave:

  • Extrae automáticamente el tipo de valor de retorno de funciones (incluidos generics), eliminando la duplicación de código.
  • Reduce la probabilidad de errores al cambiar las firmas de funciones o la estructura del valor de retorno.
  • No funciona con sobrecargas de funciones, ya que extrae solo el tipo de retorno general (amplio).

Preguntas capciosas.

¿Se puede usar ReturnType con métodos de clases?

Sí, pero es importante recordar el contexto: si el método es una propiedad de un objeto con una función, utiliza ReturnType<obj['método']>.

Ejemplo de código:

class MyClass { foo(x: number) { return x * 2; } } type FooReturn = ReturnType<MyClass['foo']>; // ¡Error de tipo! // Se necesita así: type FooReturn = ReturnType<(x: number) => number>; // number // O sacar el método como función: const obj = new MyClass(); type FooReturn2 = ReturnType<typeof obj.foo>;

¿Qué devolverá ReturnType para funciones con void/never?

Para una función con tipo declarado void, ReturnType devolverá void. Para never — never.

Ejemplo de código:

function doNothing(): void {} type Result = ReturnType<typeof doNothing>; // void

¿Funciona ReturnType con sobrecargas de funciones?

No, ReturnType extrae el tipo de retorno de la "implementación" en sí, y no de todas las sobrecargas. Si hay múltiples sobrecargas, se toma el tipo de implementación descrito.

Ejemplo de código:

function func(x: number): number; function func(x: string): string; function func(x: any): any { return x } type RT = ReturnType<typeof func>; // any

Errores de tipo y anti-patrones

  • Usar ReturnType con funciones sobrecargadas conduce a tipos inesperados.
  • Olvidar que ReturnType no calcula el tipo de retorno del valor Promise — se necesita Awaited o trabajo manual.
  • Depender completamente de ReturnType al cambiar la lógica de la función sin actualizar otras partes dependientes del código.

Ejemplo de la vida real

Caso negativo

En un proyecto se declara un tipo manual para el objeto de retorno de una función. La función cambia — el tipo no se actualiza, las llamadas fallan en tiempo de ejecución.

Ventajas:

  • Los tipos son fáciles de leer, se pueden ajustar manualmente.

Desventajas:

  • Rápidamente quedan obsoletos, aparece "drift" entre los tipos y el API real.

Caso positivo

Se pasa a utilizar ReturnType en todas partes donde se usa el valor devuelto por la función. En caso de cambios, el tipo siempre está actualizado.

Ventajas:

  • Mínima duplicación, coincidencia tipificada con la implementación real.

Desventajas:

  • Pueden surgir dificultades para entender la magia de tipos en principiantes.