Un type littéral est un type qui n'a qu'une seule valeur : "up" est un type dont le seul membre est la chaîne "up", et 404 un type dont le seul membre est le nombre 404. Seuls, ils ne servent pas à grand-chose. Réunis en une union, ils donnent une variable qui accepte un ensemble fixe de valeurs et rien d'autre.
Sans le commentaire @ts-expect-error, le dernier appel est une erreur de compilation (TS2345). Avec lui, le programme compile et l'appel s'exécute quand même en affichant moving north by 1 : les types littéraux n'existent que pour le compilateur, et à l'exécution la valeur est une chaîne ordinaire.
Littéraux string, number et boolean
N'importe quelle valeur string, number, bigint ou boolean peut s'écrire comme un type. Le compilateur n'accepte alors que cette valeur exacte.
let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;
type Port = 80 | 443 | 8080;
const port: Port = 443;
boolean n'est lui-même que l'union true | false, c'est pourquoi affiner un boolean avec if (flag) laisse false dans la branche else.
| Type littéral | Autorise | Type plus large |
|---|---|---|
"GET" | uniquement la chaîne "GET" | string |
404 | uniquement le nombre 404 | number |
10n | uniquement le bigint 10 | bigint |
true | uniquement true | boolean |
Unions de littéraux
L'usage courant est une union qui liste chaque valeur autorisée. Dans la fonction, le compilateur affine l'union au fil de vos vérifications, et chaque branche sait exactement quelle valeur elle a.
Une union de littéraux de chaîne est l'alternative habituelle à un enum en TypeScript. Elle ne coûte rien à l'exécution, les valeurs sont de simples chaînes que vous pouvez journaliser et envoyer en JSON, et une faute de frappe est une erreur de compilation. Les compromis sont détaillés dans enums.
Élargissement : let ou const
Quand TypeScript infère un type à partir d'un littéral, il regarde si la valeur peut changer. Une variable const ne peut jamais être réassignée, elle garde donc le type littéral. Une variable let le peut, son type est donc élargi au type général.
Survolez chaque nom dans l'éditeur pour voir le type inféré. Si vous voulez une let qui ne contienne que certaines valeurs, annotez-la : let mode: "light" | "dark" = "light".
Pourquoi les propriétés d'objet s'élargissent
Les propriétés d'un littéral objet sont modifiables, elles s'élargissent donc aussi, même quand l'objet est stocké dans une const. C'est la façon la plus courante de tomber sur les types littéraux par accident :
Le compilateur signale :
index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
req est inféré comme { url: string; method: string }, car du code pourrait plus tard exécuter req.method = "DELETE". Il y a trois corrections :
Une quatrième option est satisfies, qui vérifie l'objet par rapport à un type tout en gardant les types littéraux de ses propriétés.
as const
as const est une const assertion. Placez-la après une expression et le compilateur infère le type le plus étroit possible :
- les valeurs string, number et boolean gardent leurs types littéraux
- les propriétés d'objet deviennent
readonly - les littéraux de tableau deviennent des tuples
readonlyde longueur fixe
L'assertion n'agit qu'à la compilation. Le JavaScript émis est le même littéral objet sans as const : rien n'empêche donc un autre code de le modifier à l'exécution. Si vous avez besoin d'une garantie à l'exécution, appelez aussi Object.freeze.
Un type union à partir d'un tableau as const
Un motif fréquent consiste à garder les valeurs autorisées dans un seul tableau, que vous pouvez parcourir à l'exécution, et à en dériver le type union. (typeof arr)[number] signifie « le type de n'importe quel élément de arr ».
Sans as const, ROLES serait un string[] et Role un simple string. La conversion en readonly string[] dans isRole est nécessaire parce que includes sur un tuple de littéraux n'accepte que ces littéraux, alors que le but de la fonction est de tester une chaîne qui n'en est peut-être pas un. Le même motif fonctionne pour les tables clé/valeur : const Status = { Active: "active", Banned: "banned" } as const, puis type Status = (typeof Status)[keyof typeof Status].
Paramètres de type const
Une fonction générique élargit normalement les littéraux que vous lui passez. Depuis TypeScript 5.0, vous pouvez marquer un paramètre de type const, ce qui fait inférer l'argument par le compilateur comme s'il portait as const, sans demander à l'appelant de l'écrire.
C'est surtout un outil pour les auteurs de bibliothèques : les définitions de routes, les builders et les helpers de schéma l'utilisent pour que les appelants obtiennent des types précis à partir de simples littéraux.
Les sens de const
Le mot-clé const apparaît à quatre endroits différents dans du code TypeScript :
| Syntaxe | Nature | Effet |
|---|---|---|
const x = 1 | Déclaration JavaScript | la liaison ne peut pas être réassignée ; une valeur littérale garde son type littéral |
expr as const | Assertion TypeScript | type le plus étroit : littéraux, propriétés readonly, tuples readonly |
function f<const T>() | Paramètre de type TypeScript | infère les arguments comme s'ils portaient as const |
const enum E {} | Enum TypeScript | un enum dont les membres sont remplacés par leur valeur à la compilation |
Aucun d'eux ne gèle un objet à l'exécution. const obj = { a: 1 } autorise toujours obj.a = 2 ; seule la réassignation de obj lui-même est une erreur.
Questions fréquentes
Qu'est-ce qu'un type littéral en TypeScript ?
Un type qui n'autorise qu'une seule valeur. "GET" est un type dont la seule valeur est la chaîne "GET", 404 est un type dont la seule valeur est le nombre 404, et true est un type dont la seule valeur est true. Ils sont surtout utiles combinés en unions, comme type Method = "GET" | "POST".
Que fait as const en TypeScript ?
as const est une const assertion. Elle demande au compilateur d'inférer le type le plus étroit pour une expression : les valeurs string et number gardent leurs types littéraux, les propriétés d'objet deviennent readonly, et les littéraux de tableau deviennent des tuples readonly. Seul le type change ; à l'exécution, la valeur est le même objet ou tableau ordinaire, et elle n'est pas gelée.
Pourquoi TypeScript infère-t-il string au lieu de mon littéral ?
Parce que la valeur est modifiable. let x = "a" et la propriété dans { method: "GET" } peuvent être réassignées plus tard, TypeScript les élargit donc en string. Une variable const garde le type littéral "a". Pour conserver des littéraux dans un objet, annotez-le avec le type littéral, utilisez as const, ou utilisez satisfies.
Quelle est la différence entre const et as const ?
const est une déclaration JavaScript : la variable ne peut pas être réassignée, mais l'objet vers lequel elle pointe peut toujours être modifié. as const est une assertion de type TypeScript : elle rend chaque propriété de la valeur readonly et littérale dans le système de types. Aucun des deux ne gèle l'objet à l'exécution ; utilisez Object.freeze pour cela.
Comment obtenir un type union à partir d'un tableau de chaînes ?
Déclarez le tableau avec as const, puis indexez son type avec number : const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; donne "admin" | "user". Sans as const, le tableau est un string[] et le résultat n'est qu'un string.