Les règles ci-dessous sont celles qui évitent le plus de bugs dans du vrai code TypeScript. Chacune montre d'abord la version courante, puis la meilleure, dans du code que vous pouvez exécuter. La première règle est la plus importante : arrêtez d'utiliser any.
Utiliser unknown plutôt que any
any désactive la vérification des types pour une valeur et pour tout ce qui est calculé à partir d'elle. unknown accepte lui aussi n'importe quelle valeur, mais vous devez la vérifier avant de l'utiliser, ce qui place la vérification là où les données entrent dans votre programme :
Les frontières sont les endroits où les types cessent d'être garantis : JSON.parse, les réponses de fetch, localStorage, les saisies de formulaire, les variables d'environnement et les messages d'autres processus. Validez à ces endroits avec un type guard ou une bibliothèque de schémas, et le reste du code peut faire confiance à ses types.
Garder strict activé
strict est la valeur par défaut dans TypeScript 7 ; ne le désactivez pas. Ajoutez les vérifications qu'il laisse de côté et qui attrapent le plus de bugs :
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true
}
}
noUncheckedIndexedAccess fait inclure undefined à arr[i] et record[key], puisque c'est ce qu'ils renvoient pour un index absent. noImplicitOverride oblige une méthode de sous-classe à indiquer override, et noFallthroughCasesInSwitch refuse un case qui déborde sur le suivant.
Laisser l'inférence travailler
Annotez ce que TypeScript ne peut pas savoir : les paramètres de fonctions, et les types de retour des fonctions utilisées par d'autres modules. Laissez l'inférence gérer les variables locales et les paramètres des callbacks. Une annotation inutile n'est pas seulement du bruit : elle peut rendre un type plus large que la valeur :
Sans le commentaire @ts-expect-error, setStatus(annotated) provoque l'erreur TS2345. La const déduite garde le type littéral "active", elle est donc acceptée. Survolez une variable dans votre éditeur pour voir ce qui a été déduit avant d'ajouter un type.
Préférer les unions aux enums
Une union de littéraux de chaîne offre l'autocomplétion et des vérifications exhaustives sans code généré. Quand vous avez aussi besoin de la liste des valeurs à l'exécution, dérivez le type d'un tableau as const :
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"
function canEdit(role: Role): boolean {
return role !== "viewer";
}
console.log(canEdit("editor")); // true
console.log(ROLES.filter(canEdit)); // [ 'admin', 'editor' ]
canEdit("owner"); // error TS2345: Argument of type '"owner"' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
Un enum Role { Admin, Viewer } se compile en un objet avec un mappage inverse, et un paramètre de type enum numérique accepte n'importe quelle variable number, même une qui contient une valeur que l'enum ne liste pas. Les enums ne peuvent pas non plus tourner avec le type stripping de Node (TypeScript enum is not supported in strip-only mode). Les compromis sont comparés sur la page enums.
Vérifier les objets de configuration avec satisfies
Annoter un objet avec un type large comme Record<string, Route> vérifie ses valeurs mais oublie ses clés. satisfies fait la même vérification et garde le type exact :
Utilisez une annotation quand la variable doit avoir exactement le type déclaré (un paramètre de fonction, une valeur que vous réaffecterez). Utilisez satisfies pour les tables de correspondance, les tables de routes, les tokens de thème et les autres objets constants.
Modéliser l'état avec des unions discriminées
Un objet unique avec des champs optionnels autorise des états impossibles : loading: true en même temps qu'une error, ou data absent après un succès. Une union d'objets partageant une étiquette n'autorise que les états réels, et chaque branche ne voit que ses propres champs :
La ligne never est la vérification d'exhaustivité. Quand quelqu'un ajoute un état et oublie de le gérer, le build casse à cette ligne :
L'erreur est index.ts(14,13): error TS2322: Type '{ status: "cancelled"; }' is not assignable to type 'never'. Elle nomme le cas non géré. Ajoutez case "cancelled": et le code compile.
Éviter ! et as
L'assertion non nulle x! et l'assertion de type x as T demandent au compilateur d'arrêter de vérifier. Aucune ne change la valeur à l'exécution, donc une assertion fausse devient un plantage plus tard, loin de sa cause :
Remplacez ! par ?., ??, un return anticipé, ou une erreur levée avec un message utile. Remplacez as par un type guard qui teste vraiment la valeur. La seule assertion toujours sûre est as const, car elle ne fait que rendre un type plus étroit et en lecture seule. Une bonne configuration de lint (les règles no-non-null-assertion et no-explicit-any de typescript-eslint) signale le reste.
Rendre les données readonly
Marquez les propriétés et les tableaux readonly quand le code ne doit pas les modifier. Le compilateur refuse alors push, sort et les affectations, et les fonctions renvoient de nouvelles valeurs au lieu de modifier leurs entrées :
readonly n'est vérifié qu'à la compilation et sur un seul niveau : il ne gèle pas l'objet à l'exécution. Cela suffit pourtant à attraper la modification accidentelle d'un état partagé, qui est à l'origine de la plupart de ces bugs.
Questions fréquentes
Faut-il utiliser any en TypeScript ?
Presque jamais dans du code applicatif. any désactive la vérification pour la valeur et pour tout ce qui en dérive. Utilisez unknown pour les valeurs dont vous ne connaissez pas encore le type, et restreignez-le avec des tests ; gardez any pour de rares échappatoires, avec un commentaire qui explique pourquoi.
Faut-il annoter chaque variable en TypeScript ?
Non. Laissez TypeScript déduire les variables locales et les paramètres des callbacks. Annotez les paramètres de fonctions (ils ne peuvent pas être déduits), et les types de retour des fonctions exportées, pour qu'un changement à l'intérieur de la fonction ne puisse pas modifier en silence son type public.
Les enums TypeScript sont-ils une mauvaise pratique ?
Pas une erreur, mais beaucoup d'équipes les évitent. Un enum génère du code à l'exécution, ne peut pas tourner avec le type stripping de Node, et un paramètre de type enum numérique accepte n'importe quelle variable number, quelle que soit sa valeur. Une union de littéraux de chaîne, ou un tableau as const avec un type dérivé, offre la même autocomplétion et les mêmes vérifications sans code généré.
Quand utiliser les assertions de type avec as ?
Seulement quand vous savez quelque chose que le compilateur ne peut pas savoir, et de préférence juste après un test qui le prouve. as ne change aucune valeur à l'exécution, donc {} as User compile et n'a ensuite aucun name. Un type guard qui teste la valeur est l'outil le plus sûr dans la plupart des cas.
Quels réglages tsconfig choisir pour un nouveau projet TypeScript ?
Gardez strict activé (la valeur par défaut de TypeScript 7) et ajoutez noUncheckedIndexedAccess. Beaucoup de projets activent aussi noImplicitOverride, noFallthroughCasesInSwitch et verbatimModuleSyntax. La configuration que génère tsc --init règle notamment strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes et verbatimModuleSyntax.