Predicados de múltiples tipos en Typescript
Tengo un caso en el que frecuentemente reviso tanto el valor de un booleano como otro tipo de unión juntos. Aunque están lógicamente conectados y, hipotéticamente, podría revisar solo uno, necesito revisar ambos para que el compilador de TS conozca ambos tipos.
let multiple: boolean
let value: Array<string> | string
...
if (multiple && isArray(value)) {
// TS knows multiple === true and value is an Array
}
¿Hay una manera de escribir una función de comprobación de tipos donde el predicado de tipo afirme múltiples valores?
Algo así:
// Not valid Typescript
function isMulti (
multi: boolean,
value: Array<string> | string
): (multi is true) && (value is Array<string>) {
return multi && isArray(value)
}
En la mayoría de los casos puedo arreglármelas con solo comprobar isArray, pero hay casos en los que quiero reutilizar una verificación como la anterior. ¿Es esto posible?
En respuesta a @kaya3:
Para dar un ejemplo de uso más preciso, tengo algo similar a lo siguiente:
Podría asignar multiple y value a un objeto con un tipo DiscriminatedUnion, pero eso agrega más complejidad de la que creo que necesita esto.
type ValueType<M extends boolean> = M extends true ? Array<string> : string;
interface PropTypes<M extends boolean> {
multiple: M,
// more properties
}
type DiscriminatedUnion = {
multiple: true,
value: Array<string>
} | {
multiple: false,
value: string
}
function myFunction<M extends boolean>({
multiple
}: PropTypes<M>) {
let value: ValueType<M>;
// ... later
if (multiple) {
// TS will not assert the type of `value` here
}
// Trying discriminated union
// TS Error: Type '{ multiple: boolean; value: string | string[]; }' is not assignable to type '{ multiple: false; value: string; }'
let obj: DiscriminatedUnion = {
multiple,
value
}
}
Respuesta #1
Actualmente no es posible resolver tu problema de esta manera; @jcalz señala de manera útil que hay una solicitud de función abierta para ello. Dicho esto, tu problema probablemente se resuelva mejor de otra manera.
Para prácticamente cualquier pregunta como "Tengo estos dos valores, ¿cómo puedo decirle a Typescript que sus tipos están relacionados?", la respuesta es ponerlos juntos como propiedades en un objeto. Luego puedes hacer que el tipo del objeto sea una unión discriminada, de modo que una prueba sobre una de las propiedades pueda reducir el tipo de todo el objeto.
type DiscriminatedUnion =
| {multiple: false, value: string}
| {multiple: true, value: string[]}
declare let obj: DiscriminatedUnion;
if(obj.multiple) {
// here, obj.value is narrowed to string[]
obj.value
}
Enlace del parque
Respuesta #2
Con TypeScript a veces necesitas renunciar a desestructurar objetos hasta que puedan ser discriminados correctamente. A menudo exige replantear el flujo. Si no desestructuras las props de inmediato, puede inferir correctamente el tipo:
type Props = {
value: string[],
multiple: true
} | {
value: string,
multiple: false
}
function doSomethingAndDiscriminate(props:Props) {
if(props.multiple) {
const value = props.value[0]
return {somethingNew: value}
} else {
const value = props.value
return {somethingNew: value}
}
}
Enlace del parque
En este ejemplo infiere correctamente el tipo de retorno de ambas posibilidades como { somethingNew: string }
