Búsqueda de sitios web

Los genéricos de TypeScript solo inferirán tipos de unión en casos simples


Aquí hay un ejemplo de código:

declare function test_ok<T>(arr: T[]): T;
test_ok([1, 2, "hello"]); // OK
test_ok([[1], [2], ["hello"]]); // OK

// note the nested array signature
declare function test_err<T>(arr: T[][]): T;
test_err([[1], [2], ["hello"]]); // ERR type "string" is not assignable to type "number"
test_err<string | number>([[1], [2], ["hello"]]); // OK if generic is specified

Parece que, en el caso general, TypeScript puede inferir el mejor tipo común (una unión básica) cuando se le da un array heterogéneo. Sin embargo, si intentas limitar el genérico más allá de un simple array (como el array anidado mencionado arriba), renuncia. También he encontrado esto en otros casos (por ejemplo, un array de funciones donde el genérico abarca el tipo de retorno de las funciones, en lugar de las funciones completas). ¿Es esto algún tipo de optimización de rendimiento?

Respuesta #1

Voy a decir que esto es más como detección de errores intencional y no una optimización del rendimiento. Al intentar inferir T dado un conjunto de valores que se supone que son del tipo T, siempre se puede tener éxito ampliando T para que se ajuste a todos los valores, pero entonces es imposible detectar un error legítimo, donde uno de los valores se ingresó incorrectamente. Esto es una decisión de juicio por parte de los diseñadores del lenguaje, creo, y probablemente cualquier heurística produciría algunos falsos positivos y algunos falsos negativos.

El problema de GitHub microsoft/TypeScript#31617 es un informe similar, donde un usuario espera que string | number sea inferido a partir de dos argumentos, uno de tipo string y el otro de tipo number. La respuesta de uno de los mantenedores del lenguaje es:

Este es un compromiso intencional para que los genéricos puedan detectar errores cuando se espera que varios objetos sean del mismo tipo, lo cual es generalmente más común.

Entonces, ¿qué se puede hacer? Obviamente, puedes especificar manualmente T como string | number. De lo contrario, si quieres que tu función sea permisiva y nunca genere un error, puedes hacer que T sea el tipo del argumento dado. El compilador es mucho más consistente al inferir un tipo dado un valor de ese tipo que cuando se le da un valor de alguna función de ese tipo. Así que mi solución aquí sería esta para arreglos:

declare function test_fixed<T extends any[][]>(arr: T): T[number][number];
const ret = test_fixed([[1], [2], ["hello"]]); // string | number

En este caso, T está limitado a ser un arreglo doblemente anidado, y el tipo de retorno, T[number][number], es el tipo de elemento del arreglo más interno (si tomas T y buscas un valor en un índice de number, obtendrás otro tipo de arreglo... si buscas un valor de eso en un índice de number, obtienes el tipo de elemento más interno... T[number][number].)

Está bien, espero que eso ayude. ¡Buena suerte!

Enlace al código

Artículos relacionados