Dans Typescript, quel est le! (point d'exclamation / coup) quand vous déréférencer un membre?

453

En regardant le code source pour une règle tslint, je suis tombé sur la déclaration suivante:

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Remarquez l' !opérateur après node.parent. Intéressant!

J'ai d'abord essayé de compiler le fichier localement avec ma version de TS actuellement installée (1.5.3). L'erreur résultante indiquait l'emplacement exact du coup:

$ tsc --noImplicitAny memberAccessRule.ts 
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.

Ensuite, je suis passé au dernier TS (2.1.6), qui l'a compilé sans problème. Il semble donc être une fonctionnalité de TS 2.x. Mais la transpilation a complètement ignoré le bang, résultant en le JS suivant:

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Mon Google fu m'a jusqu'ici échoué.

Qu'est-ce que l'opérateur de point d'exclamation de TS et comment fonctionne-t-il?

Mike Chamberlain
la source

Réponses:

689

C'est l'opérateur d'assertion non nul. C'est une façon de dire au compilateur "cette expression ne peut pas être nullou undefinedici, alors ne vous plaignez pas de la possibilité qu'elle soit nullou undefined". Parfois, le vérificateur de type est incapable de faire cette détermination lui-même.

C'est expliqué ici :

Un nouvel !opérateur d'expression post-fix peut être utilisé pour affirmer que son opérande est non nul et non indéfini dans des contextes où le vérificateur de type n'est pas en mesure de conclure ce fait. Plus précisément, l'opération x!produit une valeur du type de xavec nullet undefinedexclu. Similaire aux assertions de type des formulaires <T>xet x as T, l' !opérateur d'assertion non nul est simplement supprimé dans le code JavaScript émis.

Je trouve l'utilisation du terme «affirmer» un peu trompeuse dans cette explication. C'est «affirmer» dans le sens où le développeur l'affirme , pas dans le sens où un test va être effectué. La dernière ligne indique en effet qu'il ne se produit aucun code JavaScript émis.

Louis
la source
102
Bon appel à l'ambiguïté «affirmer».
Estus Flask
8
Bonne explication. Je trouve que c'est une bonne pratique de faire un console.assert()sur la variable en question avant d'ajouter un !après. Parce que add !indique au compilateur d'ignorer la vérification nulle, il compile vers noop en javascript. Donc, si vous n'êtes pas sûr que la variable n'est pas nulle, il vaut mieux faire une vérification d'assertion explicite.
Jayesh
12
Comme exemple motivant: l'utilisation du nouveau type ES Map avec du code comme dict.has(key) ? dict.get(key) : 'default';le compilateur TS ne peut pas inférer que l' getappel ne renvoie jamais null / undefined. dict.has(key) ? dict.get(key)! : 'default';rétrécit le type correctement.
kitsu.eb
1
Y a-t-il de l'argot pour cet opérateur, comme la façon dont l'opérateur Elvis se réfère à l'opérateur binaire?
ebakunin
@Jayesh pourriez-vous développer la bonne pratique de console.assert (), pourriez-vous publier un exemple?
Christopher Francisco
168

La réponse de Louis est excellente, mais j'ai pensé que j'essaierais de résumer succinctement:

L'opérateur bang dit au compilateur de relâcher temporairement la contrainte "non nulle" qu'il pourrait autrement exiger. Il dit au compilateur: "En tant que développeur, je sais mieux que vous que cette variable ne peut pas être nulle pour le moment".

Mike Chamberlain
la source
85
Ensuite, en tant que développeur, vous avez foiré.
Mike Chamberlain
9
Ou, en tant que compilateur, il a foiré. Si le constructeur n'initialise pas une propriété mais qu'un hook de cycle de vie le fait et que le compilateur ne le reconnaît pas.
Mukus
26
Ce n'est pas la responsabilité du compilateur TS. Contrairement à certains autres langages (par exemple C #), JS (et donc TS) n'exige pas que les variables soient initialisées avant utilisation. Ou, pour voir les choses autrement, dans JS, toutes les variables déclarées avec varou letsont implicitement initialisées à undefined. De plus, les propriétés d'instance de classe peuvent être déclarées comme telles, elles sont donc class C { constructor() { this.myVar = undefined; } }parfaitement légales. Enfin, les hooks du cycle de vie dépendent du framework; par exemple, Angular et React les implémentent différemment. On ne peut donc pas s'attendre à ce que le compilateur TS raisonne à leur sujet.
Mike Chamberlain
1
Existe-t-il un cas d'utilisation valable pour l'opérateur bang compte tenu de la beauté de l'analyse de type basée sur le flux de contrôle dans TS?
Eugene Karataev
1
@EugeneKarataev Oui, les frameworks initialisent souvent des variables en eux-mêmes et l'analyse du flux de contrôle ts ne peut pas l'attraper. Son utilisation est certes réduite, mais vous rencontrerez des cas où vous en aurez besoin.
arg20