Typy warunkowe w TypeScript

65

Zastanawiałem się, czy mogę mieć typy warunkowe w TypeScript?

Obecnie mam następujący interfejs:

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

Ale chcę usunąć errorTexti mieć to tylko wtedy, gdy isValidjest falseto wymagana właściwość.

Chciałbym móc go napisać jako następujący interfejs:

interface ValidationResult {
  isValid: true;
}

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

Ale jak wiadomo, nie jest to możliwe. Jaki masz pomysł na tę sytuację?

Arman
źródło
Czy to znaczy, kiedy isValidjest false?
CertainPerformance,
18
isValid jest wtedy zbędny. Równie dobrze możesz mieć errorText, a jeśli errorText ma wartość null, nie ma błędu.
MTilsted,
Tak, @MTilsted, masz rację, ale musimy to zachować z powodu naszych starszych kodów.
Arman,

Odpowiedzi:

89

Jednym ze sposobów modelowania tego rodzaju logiki jest użycie typu unii, coś takiego

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

Kompilator może następnie zawęzić typ w oparciu o flagę logiczną

const getErrorTextIfPresent = (r: ValidationResult): string | null => {
  return r.isValid ? null : r.errorText
}
błędy
źródło
7
Niezła odpowiedź. I fascynujące, że kompilator może powiedzieć, że tutaj rmusi być tego typu Invalid.
sleske,
1
Nazywa się to dyskryminowanymi związkami. Całkiem fajne rzeczy: typescriptlang.org/docs/handbook/...
Umur Kontacı
41

Aby uniknąć tworzenia wielu interfejsów, które przyzwyczają się tylko do utworzenia trzeciego, możesz również zmienić bezpośrednio type:

type ValidationResult = {
    isValid: false;
    errorText: string;
} | {
    isValid: true;
};
CertainPerformance
źródło
20

Unia wykazać błędy sposób obsługi to polecam. Niemniej jednak, maszynopis nie ma czegoś znanego jako „ typy warunkowych ” i można je obsługiwać to.

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


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

To ValidationResult(co jest tak naprawdę ValidationResult<boolean>spowodowane domyślnym parametrem) jest równoważne związkowi utworzonemu w odpowiedzi na błędy lub w odpowiedzi CertainPerformance i może być używane w ten sam sposób.

Zaletą tego jest to, że możesz również przekazywać znaną ValidationResult<false>wartość, a wtedy nie będziesz musiał testować isValidtak, jakby był znany falsei errorStringistniałby. Prawdopodobnie nie jest to konieczne w przypadku tego typu - a typy warunkowe mogą być złożone i trudne do debugowania, więc prawdopodobnie nie należy ich używać niepotrzebnie. Ale mogłeś i to wydawało się warte wspomnienia.

KRyan
źródło
3
Jestem pewien, że czasami są bardzo pomocne. Ale dla mnie składnia wygląda naprawdę paskudnie.
Peilonrayz,
3
@Peilonrayz Eh, ma dobrą spójność z innymi kodowaniami maszynopisu i extendsjest właściwym operatorem do użycia. I ma bardzo silny, zwłaszcza, że można również używać go kopać w rodzaju: type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan,
@KRyan Myślałem o tym. Czy to po prostu oznacza SecondOf<number>„rozszerza się” Pair<any, number>? Wydaje mi się, że trafne jest powiedzenie „nie oceniaj książki po okładce”.
Peilonrayz,
@Peilonrayz Ah, nie; raczej przeciwnie. SecondOf<Pair<any, number>>ocenia na number. SecondOf<number>ocenia never, ponieważ number extends Pair<any, infer U>jest fałszywy, ponieważ numbernie rozszerza żadnegoPair
KRyan