Tipi condizionali in TypeScript


66

Mi chiedevo se posso avere tipi condizionali in TypeScript?

Attualmente ho la seguente interfaccia:

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

Ma voglio rimuoverlo errorTexte averlo solo quando isValidè falsecome proprietà richiesta .

Vorrei poterlo scrivere come la seguente interfaccia:

interface ValidationResult {
  isValid: true;
}

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

Ma come sai, non è possibile. Qual è la tua idea di questa situazione?


Vuoi dire, quando isValidè false?
CertainPerformance il

18
isValid è quindi ridondante. Potresti anche avere l'erroreText, e quindi se errorText è nullo, non c'è errore.
MTilsted

Sì, @MTilsted, hai ragione, ma dobbiamo mantenerlo a causa dei nostri codici legacy.
Arman,

Risposte:


89

Un modo per modellare questo tipo di logica è usare un tipo di unione, qualcosa del genere

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" }
}

Il compilatore è quindi in grado di restringere il tipo in base al flag booleano

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

7
Bella risposta. E affascinante che il compilatore può effettivamente dire che rdeve essere di tipo Invalidqui.
sleske,

1
Questo si chiama sindacati discriminati. Roba abbastanza interessante: typescriptlang.org/docs/handbook/…
Umur Kontacı

41

Per evitare di creare più interfacce che si abituano solo a crearne un terzo, puoi anche alternare direttamente, con un typeinvece:

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

20

L' unione dimostrata dai bug è come consiglio di gestirlo. Ciò nonostante, Carattere tipografico non ha qualcosa di noto come “ tipi condizionali ,” e in grado di gestire questo.

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


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

Questo ValidationResult(che in realtà è ValidationResult<boolean>dovuto al parametro predefinito) è equivalente all'unione prodotta nella risposta dei bachi o nella risposta di CertainPerformance e può essere utilizzata nello stesso modo.

Il vantaggio qui è che potresti anche passare un ValidationResult<false>valore noto e quindi non dovresti testare isValidcome sarebbe noto falsee errorStringesisterebbe. Probabilmente non è necessario per un caso come questo e i tipi condizionati possono essere complessi e difficili da eseguire il debug, quindi probabilmente non dovrebbero essere utilizzati inutilmente. Ma potresti, e questo sembrava degno di nota.


3
Sono sicuro che a volte sono davvero utili. Ma, per me, la sintassi sembra davvero cattiva.
Peilonrayz,

3
@Peilonrayz Eh, ha una buona coerenza con altri codici Typescript ed extendsè l'operatore giusto da usare. Ed ha estremamente potente, soprattutto perché si può anche usare per scavare in tipi: type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan il

@KRyan Ci ho pensato. Fondamentalmente questo significa semplicemente SecondOf<number>"espandersi" Pair<any, number>? Immagino che il detto "non giudicare un libro dalla copertina" sia pertinente qui.
Peilonrayz,

@Peilonrayz Ah, no; piuttosto il contrario. SecondOf<Pair<any, number>>valuta number. SecondOf<number>valuta never, perché number extends Pair<any, infer U>è falso, in quanto numbernon si estende a nessunoPair
KRyan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.