Tipos condicionales en TypeScript


66

Me preguntaba si puedo tener tipos condicionales en TypeScript?

Actualmente tengo la siguiente interfaz:

interface ValidationResult {
  isValid: boolean;
  errorText?: string;
}

Pero quiero eliminar errorText, y solo tenerlo cuando isValides falsecomo una propiedad requerida .

Desearía poder escribirlo como la siguiente interfaz:

interface ValidationResult {
  isValid: true;
}

interface ValidationResult {
  isValid: false;
  errorText: string;
}

Pero como sabes, no es posible. Entonces, ¿cuál es su idea sobre esta situación?


¿Quieres decir cuándo isValides false?
CertainPerformance

18
isValid es redundante entonces. También podría tener el errorText, y luego si errorText es nulo, no hay error.
MTilsted

Sí, @MTilsted, tiene razón, pero tenemos que mantenerlo debido a nuestros códigos heredados.
Arman

Respuestas:


89

Una forma de modelar este tipo de lógica es usar un tipo de unión, algo como esto

interface Valid {
  isValid: true
}

interface Invalid {
  isValid: false
  errorText: string
}

type ValidationResult = Valid | Invalid

const validate = (n: number): ValidationResult => {
  return n === 4 ? { isValid: true } : { isValid: false, errorText: "num is not 4" }
}

El compilador puede reducir el tipo basado en la bandera booleana

const getErrorTextIfPresent = (r: ValidationResult): string | null => {
  return r.isValid ? null : r.errorText
}

77
Buena respuesta. Y fascinante que el compilador realmente pueda decir que rdebe ser de tipo Invalidaquí.
sleske

1
Esto se llama sindicatos discriminados. Cosas geniales: typescriptlang.org/docs/handbook/…
Umur Kontacı

41

Para evitar crear múltiples interfaces que solo se utilizan para crear un tercero, también puede alternar directamente, con un typelugar:

type ValidationResult = {
    isValid: false;
    errorText: string;
} | {
    isValid: true;
};

20

La unión demostrada por los errores es cómo recomiendo manejar esto. No obstante, Letra de imprenta no tiene algo conocido como “ tipos condicionales ”, y que puede manejar esto.

type ValidationResult<IsValid extends boolean = boolean> = (IsValid extends true
    ? { isValid: IsValid; }
    : { isValid: IsValid; errorText: string; }
);


declare const validation: ValidationResult;
if (!validation.isValid) {
    validation.errorText;
}

Esto ValidationResult(que en realidad se ValidationResult<boolean>debe al parámetro predeterminado) es equivalente a la unión producida en la respuesta de errores o en la respuesta de CertainPerformance , y se puede usar de la misma manera.

La ventaja aquí es que también podría pasar un ValidationResult<false>valor conocido , y luego no tendría que probar isValidcomo se sabe falsey errorStringse sabe que existe. Probablemente no sea necesario para un caso como este, y los tipos condicionales pueden ser complejos y difíciles de depurar, por lo que probablemente no deberían usarse innecesariamente. Pero podrías, y parecía que valía la pena mencionarlo.


3
Estoy seguro de que estos son realmente útiles a veces. Pero, para mí, la sintaxis parece realmente desagradable.
Peilonrayz

3
@Peilonrayz Eh, tiene una buena coherencia con otras codificaciones de Typecript y extendses el operador adecuado para usar. Y tiene muy potente, sobre todo porque también se puede utilizar para profundizar en los tipos: type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan

@KRyan He estado pensando en eso. ¿Básicamente, eso solo significa SecondOf<number>'se expande' Pair<any, number>? Supongo que el dicho "no juzgues un libro por su portada" es pertinente aquí.
Peilonrayz

@Peilonrayz Ah, no; más bien lo contrario. SecondOf<Pair<any, number>>evalúa a number. SecondOf<number>evalúa a never, porque number extends Pair<any, infer U>es falso, ya numberque no extiende ningunoPair
KRyan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.