Types conditionnels dans TypeScript


66

Je me demandais si je pouvais avoir des types conditionnels dans TypeScript?

Actuellement, j'ai l'interface suivante:

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

Mais je veux retirer errorText, et seulement quand isValidest falsecomme nécessaire la propriété.

J'aimerais pouvoir l'écrire comme l'interface suivante:

interface ValidationResult {
  isValid: true;
}

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

Mais comme vous le savez, ce n'est pas possible. Alors, quelle est votre idée de cette situation?


Voulez - vous dire, quand isValidest false?
CertainPerformance

18
isValid est alors redondant. Vous pourriez aussi bien avoir simplement errorText, puis si errorText est nul, il n'y a pas d'erreur.
MTilsted

Oui, @MTilsted, vous avez raison, mais nous devons le conserver en raison de nos anciens codes.
Arman

Réponses:


89

Une façon de modéliser ce type de logique consiste à utiliser un type d'union, quelque chose comme ça

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

Le compilateur est alors en mesure de restreindre le type en fonction de l'indicateur booléen

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

7
Bonne réponse. Et fascinant que le compilateur puisse réellement dire quer doit être de type Invalidici.
sleske

1
C'est ce qu'on appelle les syndicats discriminés. Des trucs plutôt cool: typescriptlang.org/docs/handbook/…
Umur Kontacı

41

Pour éviter de créer plusieurs interfaces qui ne sont utilisées que pour en créer une troisième, vous pouvez également alterner directement, avec un à la typeplace:

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

20

L' union démontrée par les bogues est la façon dont je recommande de gérer cela. Néanmoins, Tapuscrit n'ont quelque chose appelé « types conditionnels » , et ils peuvent gérer cela.

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


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

Cette ValidationResult (qui est en fait ValidationResult<boolean>dû au paramètre par défaut) est équivalent à l'union produite dans la réponse des bogues ou dans la réponse de CertainPerformance , et peut être utilisé de la même manière.

L'avantage ici est que vous pouvez également faire circuler une ValidationResult<false>valeur connue , et vous n'aurez alors pas à tester isValidcar elle serait connue falseet errorStringexisterait. Probablement pas nécessaire pour un cas comme celui-ci - et les types conditionnels peuvent être complexes et difficiles à déboguer, donc ils ne devraient probablement pas être utilisés inutilement. Mais vous le pouviez, et cela semblait utile de le mentionner.


3
Je suis sûr que ces informations sont parfois très utiles. Mais, pour moi, la syntaxe est vraiment désagréable.
Peilonrayz

3
@Peilonrayz Eh, il a une bonne cohérence avec les autres codages Typescript et extendsest le bon opérateur à utiliser. Et il a très puissant, d' autant plus que vous pouvez également l' utiliser pour creuser dans les types: type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan

@KRyan J'y ai pensé. Est-ce que cela signifie simplement SecondOf<number>«se développe» Pair<any, number>? Je suppose que le dicton "ne jugez pas un livre par sa couverture" est pertinent ici.
Peilonrayz

@Peilonrayz Ah, non; plutôt le contraire. SecondOf<Pair<any, number>>évalue à number. SecondOf<number>évalue à never, parce que number extends Pair<any, infer U>c'est faux, comme numberne prolonge aucunPair
KRyan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.