La mayoría de las veces leo que debemos usar la inferencia de tipos tanto como sea posible. Al escribir una función, entiendo que debemos escribir argumentos ya que no se pueden inferir, pero ¿por qué tenemos que escribir el valor de retorno? TypeScript se encarga de eso. ¿Cuáles son los beneficios de escribir explícitamente el valor de retorno de una función? Hasta ahora solo leí que debería hacerlo, pero nadie dice por qué.
Aquí hay una buena razón para usar tipos de retorno explícitos. Según la wiki de TS :
Agregar anotaciones de tipo, especialmente tipos de retorno, puede ahorrarle mucho trabajo al compilador. En parte, esto se debe a que los tipos con nombre tienden a ser más compactos que los tipos anónimos (lo que el compilador podría inferir), lo que reduce la cantidad de tiempo dedicado a leer y escribir archivos de declaración (por ejemplo, para compilaciones incrementales). La inferencia de tipos es muy conveniente, por lo que no hay necesidad de hacer esto universalmente; sin embargo, puede ser útil intentarlo si ha identificado una sección lenta de su código.
Por lo tanto, si no tiene ningún problema con el rendimiento de la compilación, creo que no es necesario especificar los tipos de devolución.
Aquí puede encontrar otra pregunta/respuesta sobre el rendimiento de la compilación. También se relaciona con la wiki de rendimiento de TypeScript
PD, puede usar un tipo de retorno explícito para no permitir el uso de algunas propiedades adicionales del valor de retorno. Considere este ejemplo:
const foo = (): { age: number } => { const result = { age: 42, name: 'John' } return result } const result = foo() result.age // ok result.name //error Como habrás notado, el tipo de devolución explícito {age:number} es un supertipo de un tipo de valor de devolución. Pero es débil, porque no funciona si quieres devolver un objeto literal, como aquí:
const foo = (): { age: number } => ({ age: 42, name: 'John' // error })Así que no puedo recomendar el uso de esta técnica, sin embargo, vale la pena saberlo.
La razón principal para anotar el tipo de retorno de una función es la misma que la razón principal para usar Typescript: porque desea que el compilador verifique su código y le proporcione mensajes de error útiles cuando cometa ciertos tipos de errores.
Si permite que Typescript infiera el tipo de retorno de su función, entonces lo que infiera será el tipo de retorno de la función, incluso si el tipo inferido no es el tipo que pretendía. En este caso, si intenta devolver algo del tipo incorrecto, el compilador usará ese tipo incorrecto como el tipo de retorno de la función y obtendrá un error cuando intente llamar a la función y espere algo del tipo correcto, o peor. , no obtendrá un error, pero su código fallará en el tiempo de ejecución o hará algo incorrecto.
Por otro lado, si sabe cuál quiere que sea el tipo de devolución y desea que el compilador verifique que su función realmente devuelve algo de ese tipo, entonces debe usar una anotación de tipo. En este caso, si intenta devolver algo del tipo incorrecto, obtendrá un error en la función misma, donde realmente estaba el error.
El compilador puede inferir lo que hace su código, pero no tiene idea de lo que pretendía . Tome este ejemplo trivial:
function thing(value: string) { return value === "foo" ? 123 : "456"; } El tipo inferido es function thing(value: string): 123 | "456" , y eso coincide con la implementación, pero ¿es correcto en algún sentido significativo? Quizás tenía la intención de devolver siempre un number , por ejemplo; si le digo eso al compilador, puede decirme que no lo hice:
Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.(2322)En particular, cuando usa tipos complejos de envoltorios/genéricos (por ejemplo, veo muchos problemas en torno a esto en angular donde se usan los observables RxJS), esto realmente puede ayudar a obtener comentarios tempranos sobre sus suposiciones.
También trabajo mucho con el desarrollo basado en pruebas (TDD), donde uno de los valores de escribir pruebas antes de la implementación es que le brinda la oportunidad de analizar la interfaz en un momento en que el costo de cambiarla es casi cero. Usando el ejemplo clásico de RPS:
function rps(left: string, right: string) { return "right"; } it("returns 'right' for 'rock' vs. 'paper'", () => { expect(rps("rock", "paper")).to.equal("right"); });Eso compilará y pasará, pero ¿es eso lo que queremos ? Ahora podemos hablar de opciones:
¿Aceptamos la function rps(left: string, right: string): string ?
Sea más específico con, por ejemplo
type Throw = "rock" | "paper" | "scissors"; type Outcome = "left" | "right" : "draw"; function rps(left: Throw, right: Throw): Outcome { ... }¿Usar enumeraciones en su lugar?
Podemos hablar sobre las compensaciones y elegir la mejor opción dado lo que sabemos en ese momento. El tipo de retorno explícito sirve como documentación de la decisión que tomamos.
Recomendaría activar @typescript-eslint/explicit-function-return-type si está borrando su código, lo que proporciona su justificación:
Los tipos explícitos para los valores devueltos de la función aclaran a cualquier código de llamada qué tipo se devuelve. Esto asegura que el valor devuelto se asigne a una variable del tipo correcto; o en el caso de que no haya un valor de retorno, que el código de llamada no intente usar el valor indefinido cuando no debería.