Menu

Switch case en TypeScript : narrowing et exhaustivité

L'instruction switch en TypeScript : syntaxe, comment chaque case affine un type union, les switch exhaustifs qui refusent de compiler quand un case manque, le motif switch (true), le fallthrough et la portée des blocs.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Une instruction switch en TypeScript est le switch de JavaScript avec la vérification de types. Elle compare une valeur à chaque case avec ===, exécute la branche qui correspond, et s'arrête sur break ou return. TypeScript affine la valeur testée dans chaque case et peut vérifier que toutes les valeurs possibles sont traitées.

Les labels empilés (case "sat": case "sun":) partagent une même branche. return termine la fonction, donc aucun break n'est nécessaire après.

Syntaxe

switch (expression) {
    case value1:
        // runs when expression === value1
        break;
    case value2:
    case value3:
        // runs for value2 or value3
        break;
    default:
        // runs when nothing else matched
}
  • La comparaison utilise l'égalité stricte, === : case 1 ne correspond pas à la chaîne "1".
  • Sans break, return ni throw, l'exécution continue dans le case suivant (fallthrough).
  • default est optionnel et peut se placer n'importe où, même si on le met par convention en dernier.
  • Une valeur de case dont TypeScript peut prouver qu'elle ne correspond jamais est l'erreur TS2678. Pour un paramètre typé "a" | "b", case "c": signale Type '"c"' is not comparable to type '"a" | "b"'., ce qui attrape les fautes de frappe dans les labels.

Le narrowing dans chaque case

Dans chaque case, TypeScript sait quelle valeur a correspondu et affine le type. C'est surtout utile avec une union discriminée : faites le switch sur la propriété étiquette commune, et chaque case voit la variante correspondante avec ses propres propriétés.

shape.radius ne compile que dans le case "circle". En dehors, shape pourrait être un rectangle, qui n'a pas de radius. Il n'y a pas de default, et la fonction passe quand même la vérification avec un retour number, car TypeScript voit que les trois cases couvrent tous les kind. D'autres motifs construits sur ce principe se trouvent sur la page unions discriminées.

Switch exhaustif avec never

La fonction ci-dessus cesse de compiler si une quatrième forme est ajoutée sans case (TS2366, un return manquant). Cela ne fonctionne que si la fonction renvoie une valeur. Pour une garantie qui marche aussi dans du code void et donne un message plus clair, ajoutez un default qui affecte la valeur à never :

index.ts(14,19): error TS2322: Type '{ kind: "triangle"; base: number; height: number; }' is not assignable to type 'never'.

Après les cases traités, le seul type restant pour shape est la variante triangle, qui ne peut pas être affectée à never. L'erreur nomme exactement ce qui manque. Ajoutez case "triangle": return (shape.base * shape.height) / 2; et le default voit never : le bloc compile et affiche 9. Le throw protège toujours contre des données incorrectes à l'exécution, comme un kind venu d'un JSON que les types n'avaient pas prévu.

Beaucoup de projets enveloppent la vérification dans un helper :

La même technique fonctionne pour les enums : faites le switch sur la valeur de l'enum et passez-la à assertNever dans le default.

switch (true) pour les intervalles et les conditions

switch compare des valeurs, les intervalles demandent donc une astuce : faites le switch sur true et écrivez une condition dans chaque case. Depuis TypeScript 5.3, ces conditions affinent les types exactement comme les instructions if.

Les cases sont testés dans l'ordre : placez les plus spécifiques en premier. Que cette forme soit plus lisible que if / else if est une question de goût ; le comportement est le même.

Fallthrough et break

Un case qui contient du code mais ni break, ni return, ni throw se prolonge dans le case suivant. C'est presque toujours un bug. L'option de compilation noFallthroughCasesInSwitch (qui ne fait pas partie de strict) en fait l'erreur TS7029, Fallthrough case in switch., tout en autorisant les labels vides empilés.

Un fallthrough volontaire comme celui-ci fonctionne, mais avec noFallthroughCasesInSwitch activé il faut le réécrire, par exemple avec des if séparés. C'est de toute façon souvent plus clair.

Variables dans les cases

Tout le corps du switch forme un seul bloc, donc un const déclaré dans un case est visible dans les autres (et le redéclarer est une erreur). Entourez un case d'accolades pour lui donner sa propre portée :

Sans les accolades, le second const unit provoque l'erreur de compilation TS2451, Cannot redeclare block-scoped variable 'unit'.

switch ou objet de correspondance

Quand chaque case ne fait qu'associer une valeur à une autre, un objet typé avec Record est plus court, et TypeScript vérifie que chaque clé est présente :

Oublier une clé est une erreur de compilation, ce qui donne la même exhaustivité qu'une vérification never. Gardez switch pour les cases qui exécutent une logique différente, affinent des variantes d'union ou font un retour anticipé.

Questions fréquentes

Comment écrire une instruction switch en TypeScript ?

Exactement comme en JavaScript : switch (value) { case "a": ...; break; default: ... }. Les cases sont comparés avec ===. TypeScript ajoute des vérifications : une valeur de case qui ne peut jamais correspondre au type testé est une erreur (TS2678), et dans chaque case la variable testée est affinée selon ce case.

Comment rendre un switch exhaustif en TypeScript ?

Ajoutez un default qui affecte la valeur à une variable de type never : default: { const unreachable: never = value; throw new Error(...) }. Quand chaque membre de l'union est traité, la valeur y est never et le code compile. Quand il en manque un, le compilateur signale le membre manquant (TS2322).

Comment traiter plusieurs cases avec le même code dans un switch TypeScript ?

Empilez les labels sans code entre eux : case "sat": case "sun": return "weekend";. Les cases vides passent au suivant. L'option noFallthroughCasesInSwitch ne signale que les cases qui contiennent du code sans break ni return, donc les labels empilés restent autorisés.

switch (true) affine-t-il les types en TypeScript ?

Oui, depuis TypeScript 5.3. Dans switch (true) { case typeof x === "string": ... }, x est affiné en string à l'intérieur de ce case, exactement comme dans un if. C'est une alternative lisible à une chaîne de if/else if avec des vérifications d'intervalle ou de type.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER