Pourquoi vous devriez être prudent avec `!` en TypeScript
Les fonctionnalités de sécurité contre les valeurs nulles de TypeScript constituent l’un de ses principaux atouts. Avec strictNullChecks activé, le compilateur vous oblige à gérer les cas où une valeur pourrait être null ou undefined avant de l’utiliser. Mais il existe un seul caractère qui vous permet de contourner tout cela : !.
L’opérateur d’assertion non-null est facile à utiliser lorsque le compilateur se plaint. Comprendre ce qu’il fait réellement — et ce qu’il ne fait pas — vous évitera des bugs véritablement difficiles à traquer.
Points clés à retenir
- L’opérateur
!(assertion non-null) indique au compilateur TypeScript de traiter une valeur comme non-null, mais il ne génère aucune vérification à l’exécution — il est entièrement effacé dans le JavaScript émis. - L’utilisation excessive de
!compromet l’objectif destrictNullChecksen convertissant les erreurs de compilation en crashs à l’exécution. - Des alternatives plus sûres comme les vérifications explicites de null, le chaînage optionnel (
?.), la coalescence des nuls (??) et les gardes d’assertion personnalisées devraient être votre premier choix. - Réservez
!aux cas où vous disposez d’une connaissance externe véritable que le compilateur ne peut pas vérifier, et traitez chaque instance comme un signal d’alerte lors de la revue de code.
Ce que fait réellement l’opérateur ! de TypeScript
Lorsque vous ajoutez ! à une expression, vous dites au compilateur TypeScript : « Fais-moi confiance, cette valeur n’est ni null ni undefined. » Le compilateur supprime null et undefined du type et cesse de se plaindre.
// Avec strictNullChecks activé
const input = document.querySelector('input') // type: HTMLInputElement | null
const value = input!.value // type: string — le compilateur est satisfait
Voici la partie critique : ! est entièrement effacé dans le JavaScript émis. Il n’y a aucune vérification à l’exécution. Aucune garde. Aucun filet de sécurité. Si input est effectivement null à l’exécution, vous obtiendrez la même erreur Cannot read properties of null que vous essayiez d’éviter en premier lieu.
Assertion d’affectation définitive vs. Assertion non-null
L’opérateur ! apparaît dans deux contextes distincts, et il vaut la peine de les distinguer.
Assertion non-null — utilisée sur des expressions pour retirer null | undefined du type :
const el = document.getElementById('app')! // HTMLElement, pas HTMLElement | null
Assertion d’affectation définitive — utilisée dans les champs de classe ou les déclarations de variables pour indiquer au compilateur qu’une propriété sera affectée avant utilisation, même s’il ne peut pas le vérifier :
class UserService {
user!: User // "Je promets que cela sera affecté avant d'être lu"
}
Les deux sont des assertions au moment de la compilation qui font taire le compilateur sans ajouter aucune protection à l’exécution. L’assertion non-null supprime null | undefined du type d’une expression, tandis que l’assertion d’affectation définitive désactive les vérifications d’affectation définitive pour une variable ou un champ de classe.
Pourquoi l’utilisation excessive de ! compromet la sécurité contre les valeurs nulles de TypeScript
L’opérateur ! est une échappatoire, pas une solution. Lorsque vous l’utilisez, vous ne résolvez pas un problème de nullabilité — vous le cachez au compilateur tout en le laissant pleinement intact à l’exécution.
Un pattern courant qui cause de véritables bugs :
// Dangereux : suppose que l'élément existe toujours
const button = document.querySelector('.submit-btn')!
button.addEventListener('click', handleSubmit)
Si cet élément n’existe pas dans un contexte particulier — une page différente, un rendu conditionnel, un environnement de test — vous obtenez un crash à l’exécution. Le compilateur ne vous a donné aucun avertissement parce que vous lui avez dit de ne pas le faire.
Discover how at OpenReplay.com.
Des alternatives plus sûres à privilégier
TypeScript moderne dispose d’une analyse de flux de contrôle robuste. Dans de nombreuses situations où les développeurs utilisaient historiquement !, le compilateur peut désormais affiner les types automatiquement avec une simple garde.
Vérification explicite de null :
const button = document.querySelector('.submit-btn')
if (button) {
button.addEventListener('click', handleSubmit)
}
Chaînage optionnel avec coalescence des nuls :
const label = document.querySelector('label')?.textContent ?? 'Default'
Fonction de garde de type :
function assertExists<T>(
val: T | null | undefined,
msg: string
): asserts val is T {
if (val == null) throw new Error(msg)
}
const el = document.getElementById('app')
assertExists(el, 'App element not found')
el.style.display = 'block' // affiné vers HTMLElement
L’approche par garde d’assertion est particulièrement utile car elle préserve la garantie de sécurité — si la valeur est null, vous obtenez une erreur explicite et descriptive au point de défaillance plutôt qu’un crash cryptique quelque part en aval.
Quand ! est réellement approprié
Il existe des utilisations légitimes. Si vous disposez d’une connaissance externe que le compilateur ne peut pas inférer — par exemple, une valeur garantie par un cycle de vie d’initialisation que TypeScript ne peut pas tracer — ! est un outil raisonnable. La question clé est : savez-vous réellement quelque chose que le compilateur ne sait pas, ou êtes-vous simplement en train de faire taire un avertissement ?
Si c’est le second cas, l’avertissement a probablement raison.
Conclusion
L’opérateur ! de TypeScript ne rend pas votre code plus sûr — il le rend plus silencieux. Utilisé négligemment, il convertit les erreurs de compilation en crashs à l’exécution, ce qui est l’opposé de ce que strictNullChecks est conçu pour prévenir. Privilégiez d’abord l’affinement par flux de contrôle, le chaînage optionnel ou les gardes explicites. Réservez ! aux cas où vous avez une certitude véritable que le compilateur ne peut pas vérifier, et traitez chaque instance comme un signal d’alerte méritant d’être examiné lors de la revue de code.
FAQ
Non. Le point d'exclamation est entièrement supprimé lors de la compilation. Le JavaScript émis ne contient aucune vérification de null ni aucune garde d'aucune sorte. Si la valeur s'avère être null ou undefined à l'exécution, votre code lèvera une erreur exactement comme si vous n'aviez jamais utilisé TypeScript.
Elle peut l'être. Lorsque vous écrivez une propriété comme user!: User, vous indiquez au compilateur que la valeur sera affectée avant que tout code ne la lise. Si cette hypothèse est fausse — par exemple, si une étape d'initialisation attendue est ignorée — l'accès à la propriété retournera undefined et causera probablement une erreur à l'exécution sans aucun avertissement à la compilation.
C'est raisonnable lorsque vous disposez d'une connaissance externe que le compilateur ne peut pas vérifier. Un exemple courant est lorsqu'une valeur est garantie par un cycle de vie d'initialisation que TypeScript ne peut pas tracer (par exemple, les hooks d'initialisation de framework ou l'injection de dépendances). Même dans ce cas, envisagez d'utiliser une garde d'assertion explicite à la place, afin qu'une valeur manquante produise un message d'erreur clair plutôt qu'un crash cryptique.
La meilleure alternative dépend du contexte. Pour les recherches DOM et les données optionnelles, utilisez le chaînage optionnel avec la coalescence des nuls. Pour les cas où une valeur manquante constitue une véritable erreur, utilisez une fonction de garde d'assertion personnalisée qui lève un message descriptif. Les deux approches préservent l'affinement de type tout en ajoutant une véritable sécurité à l'exécution.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.