typescript - ¿Por qué una unión de tuplas espera `never` como argumento de ` .includes()`?
type Word = "foo" | "bar" | "baz";
const schema = {
foo: ["foo"] as const,
bar: ["bar"] as const,
baX: ["bar", "baz"] as const,
};
const testFn = (schemaKey: keyof typeof schema, word: Word) => {
const array = schema[schemaKey]
array.includes(word);
// ^
// TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
// Type 'string' is not assignable to type 'never'
}
Enlace al parque
¿Por qué sucede esto? Me parece simplemente tonto... ¿o hay algo que estoy pasando por alto? ¿Cómo puedo solucionarlo de manera razonable? ¿Solo afirmo nunca, o hay advertencias ocultas?
Respuesta #1
Echemos un vistazo a algunos de los tipos. Tenemos
array: readonly ["foo"] | readonly ["bar"] | readonly ["bar", "baz"]
Ahora, el método include en un T[] toma un argumento del tipo T, es decir, un argumento del tipo correcto para la lista. Esto significa que, en Typescript, no está bien tipado preguntar si una cadena es un elemento de un arreglo de enteros, ya que ese tipo de código probablemente sea un error desde el principio.
Ahora, no tenemos un arreglo. Tenemos una unión. Solo puedes llamar a un método en una unión si el método es aplicable a todas las opciones de la unión.
["foo"] tiene un método include que toma un "foo" como argumento (es decir, el tipo cuyo único habitante es la cadena literal "foo"). Del mismo modo, ["bar"] tiene un método include que toma un "bar" como argumento, y ["bar", "baz"] tiene un include que toma un elemento del tipo "bar" | "baz" como argumento.
Pero no sabemos cuál de estos tenemos. Podríamos tener cualquiera de ellos. Así que nuestro include tiene que aceptar algo que sea válido para los tres. Es decir, nuestro include debe aceptar un argumento de tipo
"foo" & "bar" & "baz"
Así que podemos pasar cualquier cadena que sea igual a las tres cadenas anteriores. No conozco ninguna cadena así, y tampoco el compilador de Typescript, por lo que correctamente dice que ese tipo no tiene valores, es decir, never. No hay una manera segura con tipos de llamar a include.
Puedes hacer un any-cast para solucionarlo, como puedes hacer con todo en Typescript. Pero la técnica más inteligente es mantener la seguridad de tipos. Mira, Typescript está infiriendo un tipo demasiado específico para tu variable array, un problema común cuando empiezas a tratar con tipos literales. Pero parece que deseas que el tipo sea
array : readonly Word[]
lo cual, como sucede, es un supertipo perfectamente válido del tipo que indiqué al principio de esta publicación. Simplemente podemos usar una declaración de tipo explícita para que el código funcione
const array: readonly Word[] = schema[schemaKey]
Tenga en cuenta que esto no es un casting. No le estamos diciendo al sistema de tipos "Sé más que tú, cállate". readonly Word[] es un supertipo perfectamente válido de la unión literal que Typescript estaba infiriendo. Este código todavía se verifica en cuanto a tipos y es seguro en cuanto a tipos. Solo tuvimos que darle al compilador una pequeña pista sobre nuestras intenciones.
