Menu

Point d'exclamation en TypeScript : l'assertion non nulle (!)

Un point d'exclamation après une valeur, comme user!, est l'opérateur d'assertion non nulle : il retire null et undefined du type sans aucune vérification à l'exécution. Découvrez ce que fait x!, les formes d'assignation définie let x!: T et prop!: T, pourquoi elles sont risquées, et des alternatives plus sûres.

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

Un point d'exclamation après une expression, value!, est l'opérateur d'assertion non nulle. Il retire null et undefined du type, si bien qu'un number | undefined peut être utilisé comme un number. C'est une promesse faite au compilateur, pas une vérification : rien ne se passe à l'exécution.

Sans le !, tea.toFixed(2) provoque l'erreur TS18048, 'tea' is possibly 'undefined'. Avec lui, le code compile parce que vous avez dit au compilateur que la clé existe.

Ce que fait x! à l'exécution : rien

Le ! est effacé de la sortie. Le JavaScript compilé pour prices.get("coffee")! est simplement prices.get("coffee"). Si l'assertion est fausse, l'erreur apparaît plus tard, au premier endroit où la valeur manquante est utilisée :

Le programme affiche Cannot read properties of undefined (reading 'toFixed'). Le plantage a lieu sur la ligne qui suit le !, et dans du vrai code il peut survenir bien plus loin : le undefined peut être stocké dans un objet et exploser dans un autre fichier. C'est cette distance qui rend ! risqué.

Assignation définie : let x!: T

Le même symbole dans une déclaration a un sens voisin. TypeScript suit si une variable est assignée avant d'être lue, et il ne peut pas suivre une assignation faite dans une autre fonction :

index.ts(9,13): error TS2454: Variable 'config' is used before being assigned.

let config!: { port: number }; est une assertion d'assignation définie : « elle sera assignée avant toute lecture ». Elle corrige l'erreur, avec le même inconvénient que x! : si init() est un jour sauté, la lecture obtient undefined. Restructurer vaut en général mieux, par exemple const config = init(); avec un init qui renvoie l'objet.

Propriétés de classe : prop!: T

Avec strictPropertyInitialization (qui fait partie de strict), chaque propriété de classe doit être initialisée dans sa déclaration ou dans le constructeur. Sinon, vous obtenez TS2564 : Property 'socket' has no initializer and is not definitely assigned in the constructor. Quand une propriété est définie plus tard par une méthode ou un framework, prop!: T demande au compilateur de l'accepter :

Le premier console.log montre la faille : le type dit que socket est toujours là, mais avant open() il vaut undefined. Appeler c.socket.send à ce moment compile et lève une exception. Si la propriété peut réellement manquer, déclarez-la socket?: ... et vérifiez-la, ou créez l'objet dans le constructeur. Le principal cas où prop! est une pratique standard est un framework qui remplit une propriété après la construction : le @ViewChild(...) child!: ChildDirective d'Angular (défini avant l'exécution de ngAfterViewInit), ou les classes d'entités d'un ORM dont la bibliothèque remplit les colonnes en chargeant une ligne (la documentation de MikroORM écrit @Property() title!: string).

Des alternatives plus sûres

La plupart des ! peuvent être remplacés par quelque chose que le compilateur vérifie, ou par une vérification qui échoue bruyamment au bon endroit :

Les deux helpers affinent le type comme le fait !, mais une hypothèse fausse produit missing HOST sur-le-champ au lieu d'une TypeError ailleurs. assertDefined est une fonction d'assertion (asserts value is ...) : après l'appel, le compilateur traite host comme un string. D'autres motifs dans type guards.

Au lieu deÉcrivezCe qui se passe quand la valeur manque
user!.nameif (user) { user.name }le bloc est sauté
user!.nameuser?.nameundefined
count!count ?? 0la valeur par défaut est utilisée
map.get(k)!must(map.get(k), "k")une erreur claire à cette ligne
let x!: Tconst x = compute()rien ne peut mal tourner

Les autres points d'exclamation

! n'a pas le même sens selon sa position :

CodeSignification
value! (après une expression)assertion non nulle, TypeScript uniquement
let x!: T, prop!: Tassertion d'assignation définie, TypeScript uniquement
!value (avant une expression)NON logique, JavaScript pur
!!valueconversion en booléen, JavaScript pur
a !== b, a != binégalité, JavaScript pur

Seuls les deux premiers sont effacés à la compilation. !value et !!value s'exécutent et renvoient un booléen.

Questions fréquentes

Que signifie un point d'exclamation après une variable en TypeScript ?

value! est l'opérateur d'assertion non nulle. Il indique au compilateur que value ne vaut ni null ni undefined, et son type perd donc ces deux membres : string | undefined devient string. Il est retiré du JavaScript émis et n'ajoute aucune vérification à l'exécution : si vous vous trompez, le programme échoue plus tard avec une TypeError.

Quelle est la différence entre ! et ? en TypeScript ?

x! affirme que la valeur est présente et vous donne le type non nul, sans vérification. x?.y vérifie à l'exécution : si x vaut null ou undefined, l'expression s'arrête et renvoie undefined. Dans une déclaration, name?: string rend une propriété optionnelle, alors que name!: string indique qu'une propriété obligatoire sera assignée quelque part où le compilateur ne peut pas le voir.

Que signifie let x!: string ?

C'est une assertion d'assignation définie. Elle indique au compilateur que la variable sera assignée avant d'être lue, même s'il ne peut pas le prouver (par exemple, l'assignation a lieu dans une autre fonction). Sans elle, lire la variable provoque l'erreur TS2454, Variable 'x' is used before being assigned.

Comment corriger « has no initializer and is not definitely assigned in the constructor » ?

C'est l'erreur TS2564 de strictPropertyInitialization. Donnez une valeur initiale à la propriété, assignez-la dans le constructeur, rendez-la optionnelle (prop?: T), ou, si un framework ou une méthode d'initialisation la définit vraiment avant usage, écrivez prop!: T. Le ! est la dernière option, car rien ne vérifie la promesse.

L'opérateur d'assertion non nulle est-il une mauvaise pratique ?

Il n'est pas faux, mais chaque ! est une affirmation non vérifiée. Des configurations de lint comme @typescript-eslint/no-non-null-assertion le signalent. Préférez une vérification qui affine le type (if (x), x ?? fallback, x?.y) ou un helper qui lève une erreur claire. Gardez ! pour les endroits où la valeur est garantie par une logique que le compilateur ne peut pas suivre.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER