Pourquoi ce sujet fait-il exploser Stack Overflow ?
\n\nSi vous avez déjà codé en TypeScript et tenté de créer un objet où toutes les valeurs doivent être du même type, vous avez forcément rencontré ce problème. La question "Enforcing the type of the indexed members of a Typescript object" cumule des milliers de vues sur Stack Overflow pour une bonne raison : c'est un classique pour les développeurs venant du JavaScript.
\n\nImaginez : vous avez un dictionnaire de cache, une configuration avec des clés dynamiques, ou simplement une map de nombres. Vous voulez que TypeScript vérifie que chaque valeur de l'objet est une chaîne, un nombre ou un type complexe. Mais le compilateur laisse passer des erreurs. Ça vous parle ?
\n\nDans cet article, nous allons décortiquer toutes les approches principales pour résoudre ce problème : des simples Index Signatures aux Mapped Types avancés. Vous saurez quand utiliser chaque méthode et quels pièges éviter.
Le problème : un objet avec des clés dynamiques
\n\nPrenons un système de gestion d'utilisateurs. Nous avons un objet où les clés sont les ID des utilisateurs et les valeurs leurs noms :
\n\ninterface UserCache {\n [userId: string]: string;\n}\n\nconst cache: UserCache = {\n 'user_1': 'Alice',\n 'user_2': 'Bob',\n // 'user_3': 123; // Erreur ! Doit être une chaîne\n};\n\ncache['user_4'] = 'Charlie'; // OK\ncache['user_5'] = 42; // Erreur ! Le type 'number' n'est pas assignable à 'string'\n\n\nÇa semble fonctionner. Mais il y a un détail : Index Signature autorise n'importe quelle clé de type string. Et si on veut limiter les clés autorisées ?
Approche 1 : Index Signature — le classique
\n\nL'Index Signature est la manière la plus simple de typer toutes les valeurs d'un objet. Syntaxe :
\n\ninterface StringMap {\n [key: string]: string;\n}\n\nconst map: StringMap = {\n hello: 'world',\n foo: 'bar',\n};\n\n\nAvantages :
\n- \n
- Simplicité et lisibilité \n
- Fonctionne avec un nombre illimité de clés \n
Inconvénients :
\n- \n
- Impossible de restreindre l'ensemble des clés \n
- Toutes les valeurs doivent être du même type (ou union) \n
- Ne gère pas bien les valeurs
undefined(prudence requise) \n
Erreur fréquente des débutants : essayer d'ajouter une méthode à un tel objet :
\n\ninterface BadMap {\n [key: string]: string;\n length: number; // Erreur ! 'number' n'est pas assignable à 'string'\n}\n\n\nCela ne fonctionne pas car length viole le type commun. Pour mélanger les types, utilisez une union ou les Mapped Types.
Approche 2 : Record — l'utilitaire intégré
\n\nTypeScript fournit le type utilitaire Record<K, V> qui fait la même chose, mais avec une syntaxe plus concise :
type StringRecord = Record<string, string>;\n\nconst config: StringRecord = {\n apiUrl: 'https://api.example.com',\n timeout: '5000', // OK, chaîne\n // port: 8080, // Erreur !\n};\n\n// On peut restreindre les clés :\ntype AllowedKeys = 'name' | 'email' | 'phone';\ntype UserRecord = Record<AllowedKeys, string>;\n\nconst user: UserRecord = {\n name: 'Alice',\n email: 'alice@example.com',\n phone: '+1234567890',\n // age: '30', // Erreur ! 'age' n'est pas dans AllowedKeys\n};\n\n\nQuand l'utiliser :
\n- \n
- Pour créer rapidement un dictionnaire typé \n
- Quand les clés sont un ensemble spécifique de valeurs (union) \n
- Pour les configurations avec des champs connus \n
Inconvénient : Record ne permet pas d'avoir des types différents pour des clés différentes. Pour cela, utilisez les Mapped Types.
Approche 3 : Map — l'alternative ES6
\n\nSi vous utilisez Map de JavaScript, TypeScript le type parfaitement