Comprendre le rétrécissement de types dans TypeScript
Vous avez écrit un garde de type, mais TypeScript continue de se plaindre que la propriété n’existe pas. Ou vous avez filtré un tableau, mais le type résultant reste une union. Ces frustrations proviennent d’un décalage entre votre compréhension du fonctionnement du rétrécissement et le fonctionnement réel de l’analyse de flux de contrôle de TypeScript.
Cet article construit un modèle mental clair du rétrécissement de types TypeScript—comment le compilateur suit les types à travers votre code et quand il perd cette information.
Points clés à retenir
- Le rétrécissement de types est l’analyse de flux de contrôle de TypeScript qui suit les types à travers les chemins d’exécution en se basant sur les vérifications d’exécution qu’il peut valider
- Les mécanismes de rétrécissement principaux incluent
typeof,instanceof, les vérifications de véracité, les vérifications d’égalité, l’opérateurinet les unions discriminées - Les gardes de types définis par l’utilisateur avec des prédicats de type permettent un rétrécissement personnalisé, mais TypeScript ne vérifie pas la logique de votre prédicat
- Le type
neverpermet la vérification d’exhaustivité à la compilation pour détecter les cas non traités - Le rétrécissement échoue aux frontières des callbacks, lors de réaffectations de propriétés et dans les schémas d’alias complexes
Comment fonctionne l’analyse de flux de contrôle
Le rétrécissement TypeScript n’est pas magique. C’est le compilateur qui suit les chemins d’exécution de votre code et met à jour les informations de type en fonction des vérifications d’exécution qu’il peut valider.
Lorsque vous écrivez une condition, TypeScript analyse ce qui doit être vrai dans chaque branche :
function process(value: string | number) {
if (typeof value === 'string') {
// TypeScript sait : value est string ici
return value.toUpperCase()
}
// TypeScript sait : value est number ici
return value.toFixed(2)
}
Le compilateur voit la vérification typeof, la reconnaît comme une construction de rétrécissement et affine le type en conséquence. Cela se produit automatiquement pour plusieurs opérateurs JavaScript.
Mécanismes de rétrécissement principaux
typeof et instanceof
L’opérateur typeof rétrécit les types primitifs de manière fiable. TypeScript comprend ses particularités—y compris le fait que typeof null retourne "object".
function handle(x: unknown) {
if (typeof x === 'string') return x.length
if (typeof x === 'number') return x.toFixed(2)
}
Pour les instances de classe, instanceof fournit un rétrécissement similaire :
if (error instanceof TypeError) {
console.log(error.message)
}
Véracité et égalité
Les vérifications de véracité éliminent null et undefined :
function greet(name: string | null) {
if (name) {
return `Hello, ${name}` // name est string
}
}
Les vérifications d’égalité rétrécissent vers des types littéraux :
type Status = 'pending' | 'complete' | 'failed'
function handle(status: Status) {
if (status === 'complete') {
// status est 'complete'
}
}
L’opérateur in et les unions discriminées
L’opérateur in vérifie l’existence d’une propriété, permettant des patterns de duck-typing :
if ('radius' in shape) {
return Math.PI * shape.radius ** 2
}
Les unions discriminées combinent cela avec des types littéraux pour des patterns de rétrécissement puissants :
type Result<T> =
| { success: true; data: T }
| { success: false; error: string }
function handle<T>(result: Result<T>) {
if (result.success) {
return result.data // TypeScript sait que data existe
}
return result.error // TypeScript sait que error existe
}
Discover how at OpenReplay.com.
Gardes de types définis par l’utilisateur
Lorsque les vérifications intégrées ne suffisent pas, les prédicats de type vous permettent de définir des gardes personnalisés :
function isString(value: unknown): value is string {
return typeof value === 'string'
}
Une mise en garde : TypeScript ne vérifie pas la logique de votre prédicat. Vous affirmez la relation—si votre vérification est incorrecte, les types seront incorrects.
TypeScript moderne (5.5+) s’est amélioré ici. Array.filter peut maintenant inférer automatiquement des prédicats de type pour les callbacks simples, éliminant un point de friction courant :
const mixed: (string | number)[] = ['a', 1, 'b', 2]
const strings = mixed.filter(x => typeof x === 'string')
// TypeScript infère string[] dans de nombreux cas
Vérification d’exhaustivité avec never
Le type never permet la vérification d’exhaustivité à la compilation :
type Circle = { kind: 'circle'; radius: number }
type Square = { kind: 'square'; side: number }
type Triangle = { kind: 'triangle'; base: number; height: number }
type Shape = Circle | Square | Triangle
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2
case 'square': return shape.side ** 2
case 'triangle': return 0.5 * shape.base * shape.height
default:
const _exhaustive: never = shape
return _exhaustive
}
}
Si vous ajoutez une nouvelle variante de forme, TypeScript génère une erreur lors de l’affectation à never—vous forçant à la gérer.
Ce que le rétrécissement n’est pas
Deux constructions souvent confondues avec le rétrécissement méritent une clarification :
Les assertions de type (as) contournent entièrement le système de types. Elles ne rétrécissent pas—elles surchargent.
L’opérateur satisfies valide qu’une expression correspond à un type sans le modifier. Utile pour détecter les erreurs, mais ce n’est pas un mécanisme de rétrécissement.
Quand le rétrécissement échoue
L’analyse de flux de contrôle de TypeScript a des limites. Le rétrécissement ne persiste pas à travers :
- Les frontières de callback (bien que les fermetures avec des variables
constpréservent maintenant le rétrécissement dans TypeScript 5.4+) - Les réaffectations de propriétés entre les vérifications
- Les patterns d’alias complexes
Lorsque le rétrécissement échoue de manière inattendue, vérifiez si le compilateur peut réellement tracer le flux de contrôle depuis votre vérification jusqu’à votre utilisation.
Conclusion
Pensez au rétrécissement comme TypeScript observant vos vérifications d’exécution et mettant à jour ses connaissances en conséquence. Le compilateur est conservateur—il ne rétrécit que lorsqu’il peut prouver que l’affinement est valide.
Écrivez des vérifications que TypeScript peut suivre. Préférez les unions discriminées aux assertions de type. Utilisez la vérification d’exhaustivité pour détecter les cas manquants à la compilation plutôt qu’à l’exécution.
L’objectif n’est pas de combattre le système de types mais de structurer le code pour que le rétrécissement fonctionne naturellement.
FAQ
L'analyse de flux de contrôle de TypeScript ne maintient pas le rétrécissement à travers les frontières de callback car le callback pourrait s'exécuter plus tard lorsque le type de la variable aurait pu changer. Pour contourner cela, affectez la valeur rétrécie à une variable const avant le callback, ou utilisez un garde de type à l'intérieur du callback lui-même.
Un garde de type est une vérification d'exécution que TypeScript reconnaît et utilise pour rétrécir les types en toute sécurité. Une assertion de type utilisant 'as' indique à TypeScript de traiter une valeur comme un type spécifique sans aucune vérification d'exécution. Les gardes de type sont plus sûrs car ils impliquent des vérifications réelles, tandis que les assertions peuvent masquer des bugs si votre hypothèse est incorrecte.
Utilisez des unions discriminées lorsque vous avez des types liés qui partagent une propriété littérale commune comme 'kind' ou 'type'. Elles fournissent un rétrécissement automatique dans les instructions switch et permettent la vérification d'exhaustivité. Les gardes de type sont meilleurs pour valider des données externes ou lorsque vous ne pouvez pas modifier les types avec lesquels vous travaillez.
Cela signifie généralement que TypeScript a perdu la trace de votre rétrécissement. Les causes courantes incluent la vérification d'une propriété puis son accès via une référence différente, la réaffectation de la variable entre la vérification et l'utilisation, ou la vérification se produisant dans une portée différente. Rapprochez votre vérification de type de l'endroit où vous utilisez la valeur, ou stockez la valeur rétrécie dans une nouvelle variable const.
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.