Bedingte Typen in TypeScript

65

Ich habe mich gefragt, ob ich bedingte Typen in TypeScript haben kann.

Derzeit habe ich die folgende Schnittstelle:

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

Aber ich möchte entfernen errorTextund habe es nur, wenn isValides falseals erforderliche Eigenschaft ist.

Ich wünschte, ich könnte es als folgende Schnittstelle schreiben:

interface ValidationResult {
  isValid: true;
}

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

Aber wie Sie wissen, ist das nicht möglich. Was ist Ihre Vorstellung von dieser Situation?

Arman
quelle
Meinst du, wann isValidist false?
Bestimmte Leistung
18
isValid ist dann redundant. Sie können auch nur den errorText verwenden. Wenn errorText dann null ist, liegt kein Fehler vor.
MTilsted
Ja, @MTilsted, Sie haben Recht, aber wir müssen es aufgrund unserer alten Codes behalten.
Arman

Antworten:

89

Eine Möglichkeit, diese Art von Logik zu modellieren, besteht darin, einen Unionstyp zu verwenden

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

Der Compiler kann dann den Typ basierend auf dem Booleschen Flag eingrenzen

const getErrorTextIfPresent = (r: ValidationResult): string | null => {
  return r.isValid ? null : r.errorText
}
Fehler
quelle
7
Gute Antwort. Und faszinierend, dass der Compiler tatsächlich erkennen kann, dass rdies hier vom Typ sein muss Invalid.
Sleske
1
Dies nennt man diskriminierte Gewerkschaften. Ziemlich cool stuff: typescriptlang.org/docs/handbook/...
Umur Kontacı
41

Um zu vermeiden, dass mehrere Schnittstellen erstellt werden, die nur zum Erstellen einer dritten verwendet werden, können Sie typestattdessen auch direkt abwechseln :

type ValidationResult = {
    isValid: false;
    errorText: string;
} | {
    isValid: true;
};
Bestimmte Leistung
quelle
20

Die durch Fehler demonstrierte Vereinigung ist, wie ich empfehle, damit umzugehen. Trotzdem hat Typescript so etwas wie " bedingte Typen ", und sie können damit umgehen.

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


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

Dies ValidationResult(was tatsächlich ValidationResult<boolean>auf den Standardparameter zurückzuführen ist) entspricht der Vereinigung, die in der Antwort von Bugs oder in der Antwort von CertainPerformance erzeugt wird , und kann auf dieselbe Weise verwendet werden.

Der Vorteil hierbei ist, dass Sie auch einen bekannten ValidationResult<false>Wert weitergeben können und dann nicht testen müssen, isValidwie es bekannt ist falseund errorStringbekanntermaßen existiert. In einem solchen Fall wahrscheinlich nicht erforderlich - und bedingte Typen können komplex und schwer zu debuggen sein, sodass sie wahrscheinlich nicht unnötig verwendet werden sollten. Aber Sie konnten, und das schien erwähnenswert.

KRyan
quelle
3
Ich bin sicher, dass diese manchmal wirklich hilfreich sind. Aber für mich sieht die Syntax wirklich böse aus.
Peilonrayz
3
@Peilonrayz Eh, es hat eine gute Konsistenz mit anderen Typescript-Codierungen und extendsist der richtige Operator. Und es ist extrem leistungsfähig, zumal Sie es auch verwenden können, um in Typen zu graben : type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan
@KRyan Ich habe darüber nachgedacht. Bedeutet das im Grunde nur SecondOf<number>"erweitert" Pair<any, number>? Ich denke, das Sprichwort "Beurteile ein Buch nicht nach seinem Einband" ist hier relevant.
Peilonrayz
@Peilonrayz Ah, nein; eher das Gegenteil. SecondOf<Pair<any, number>>bewertet zu number. SecondOf<number>bewertet never, weil number extends Pair<any, infer U>falsch ist, wie numberkeine verlängertPair
KRyan