Menu

Inférence de type en TypeScript : quand annoter, quand s'abstenir

TypeScript déduit la plupart des types à partir des valeurs que vous écrivez. Découvrez ce qu'il infère pour les variables, let contre const, les objets, les tableaux et les valeurs de retour, comment les callbacks reçoivent leurs types du contexte, et où une annotation reste nécessaire.

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

L'inférence de type, c'est TypeScript qui déduit un type que vous n'avez pas écrit. Initialisez une variable, et elle prend le type de la valeur ; renvoyez une valeur depuis une fonction, et le type de retour suit. La plupart du code TypeScript contient bien moins d'annotations qu'on ne l'imagine.

Les commentaires indiquent ce qu'affiche un éditeur au survol de chaque nom. La dernière ligne est une erreur de compilation (TS2322) alors que count n'a jamais été annoté ; // @ts-expect-error la marque comme attendue pour que le reste du bloc s'exécute.

let ou const : l'élargissement des littéraux

Une const ne peut jamais changer, TypeScript lui donne donc le type le plus étroit : la valeur littérale elle-même. Une let reçoit le type plus large, car elle peut être réassignée plus tard.

let a = "left";    // string
const b = "left";  // "left"
let c = 42;        // number
const d = 42;      // 42
const e = true;    // true

Cela compte quand une fonction n'accepte que certaines valeurs :

index.ts(7,6): error TS2345: Argument of type 'string' is not assignable to parameter of type '"left" | "right"'.

Corrigez avec const, ou en annotant la variable : let dir: "left" | "right" = "left". Les types littéraux ont leur propre page, types littéraux.

Les objets et les tableaux élargissent leurs membres

Les propriétés d'un littéral objet sont modifiables, elles s'élargissent donc comme une let, même quand l'objet lui-même est const. Les éléments de tableau font de même. as const rend tout readonly et conserve les types littéraux.

L'inférence du type de retour

Le type de retour d'une fonction est inféré à partir de ses instructions return. Quand il y en a plusieurs, le résultat est leur union.

Les types de retour inférés conviennent à la plupart des fonctions. Écrivez explicitement le type de retour des fonctions exportées ou publiques quand vous voulez que la signature soit un contrat : une modification dans le corps qui renvoie un autre type échoue alors au niveau de la fonction, et non chez un appelant lointain.

Le typage contextuel : les callbacks reçoivent leurs types

Les paramètres ne sont pas inférés à partir de la façon dont une fonction est appelée. Il y a deux exceptions. Un paramètre avec une valeur par défaut prend le type de cette valeur (function retry(times = 3) fait de times un number). Et une fonction écrite là où un type de fonction est déjà attendu reçoit ses types de paramètres de ce contexte, si bien que les paramètres des callbacks n'ont pas besoin d'annotation.

Il en va de même pour forEach, filter, reduce, les callbacks de Promise, les gestionnaires d'événements et toute fonction passée à un paramètre dont le type de fonction est déclaré.

Les paramètres ont besoin d'annotations

Sans valeur par défaut ni contexte de ce genre, TypeScript ne regarde pas comment une fonction est appelée pour deviner les types de ses paramètres. Avec strict activé, un paramètre non annoté provoque l'erreur TS7006 :

index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.

Écrivez function double(x: number) et le bloc affiche 42, avec le type de retour number inféré à partir de x * 2.

Le meilleur type commun

Pour un littéral de tableau contenant différents genres de valeurs, TypeScript infère un tableau de l'union de leurs types. Il ne cherche pas un type de base commun que vous n'auriez pas mentionné.

Le tableau vide est le seul cas à surveiller. const list = [] commence comme un tableau « évolutif » dont le type grandit à chaque push ; une annotation, const list: string[] = [], énonce l'intention dès le départ et c'est plus clair.

Quand annoter

SituationAnnoter ?
Variable initialisée avec une valeurNon, la valeur donne le type
Variable déclarée sans valeur (let result;)Oui, let result: string;
Paramètres de fonctionOui (sauf les callbacks avec contexte, ou avec une valeur par défaut)
Type de retour d'une petite fonction interneFacultatif
Type de retour d'une fonction exportée ou publiqueRecommandé, comme contrat
Tableau ou objet vide que vous remplissez plus tardOui, const ids: number[] = []
La valeur doit garder ses types littérauxUtilisez as const
La valeur doit respecter un type tout en gardant son propre type inféréUtilisez satisfies

Le moyen le plus rapide de voir ce qui a été inféré est de survoler le nom dans votre éditeur. Sans éditeur, assignez la valeur à une variable d'un mauvais type, comme const probe: boolean = mixed;, et le message d'erreur du compilateur nomme le type inféré.

Questions fréquentes

Qu'est-ce que l'inférence de type en TypeScript ?

C'est le compilateur qui déduit un type que vous n'avez pas écrit. let count = 0 donne à count le type number, et une fonction qui renvoie a + b sur deux nombres reçoit le type de retour number. Le type inféré est vérifié exactement comme un type écrit.

Faut-il annoter chaque variable en TypeScript ?

Non. Annotez les paramètres de fonction (ils ne sont pas inférés à partir des appels, seulement d'une valeur par défaut ou du contexte d'un callback), les types de retour des fonctions exportées si vous voulez une API stable, et les variables déclarées sans valeur. Laissez TypeScript inférer le reste : const name: string = "Ada" répète ce que le compilateur sait déjà.

Pourquoi const infère-t-il un type littéral alors que let infère string ?

Une const ne peut jamais être réassignée, donc const dir = "left" reçoit le type littéral "left". Une let pourrait plus tard contenir n'importe quelle autre chaîne, donc let dir = "left" est élargi en string. Le même élargissement s'applique aux propriétés d'un littéral objet, car les propriétés peuvent être réassignées ; as const l'empêche.

Pourquoi TypeScript dit-il qu'un paramètre a implicitement le type 'any' ?

C'est l'erreur TS7006 de noImplicitAny, qui fait partie de strict. TypeScript n'infère pas les types des paramètres à partir de la façon dont une fonction est appelée, donc un paramètre nu serait any. Ajoutez une annotation, function double(x: number), ou passez la fonction là où son type est connu par le contexte, comme un callback de map.

Comment voir le type que TypeScript a inféré ?

Survolez le nom dans votre éditeur (VS Code et la plupart des éditeurs affichent le type inféré dans une infobulle). Dans le code, assigner la valeur à une variable d'un type manifestement faux pousse le compilateur à afficher le type inféré dans son message d'erreur.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER