Réflexion et introspection en TypeScript : comment le code se regarde lui-même
\n\nImaginez un programme capable d'analyser lui-même à l'exécution. Il peut connaître les types de ses variables, les propriétés de ses objets, et même prendre une décision basée sur ces informations. Cela ressemble à de la magie ? En réalité, il s'agit de réflexion et d'introspection — des techniques puissantes de métaprogrammation disponibles en TypeScript.
\n\nDans cet article, nous verrons la différence entre réflexion et introspection, comment les utiliser en TypeScript, et pourquoi cela peut être utile dans des projets réels. Pas de théorie ennuyeuse — seulement du code vivant et des exemples pratiques.
\n\nQu'est-ce que l'introspection et la réflexion ?
\n\nMettons les points sur les i. En programmation, ces termes sont souvent confondus, mais il y a une différence :
\n\n- \n
- Introspection — c'est la capacité d'un programme à observer sa propre structure. Vous pouvez connaître les propriétés d'un objet, son type, mais vous ne pouvez pas les modifier. \n
- Réflexion — c'est un niveau plus avancé : le programme non seulement se regarde, mais modifie aussi son comportement à l'exécution. Par exemple, appeler dynamiquement des méthodes ou créer de nouvelles instances de classes. \n
En JavaScript, sur lequel TypeScript est basé, la réflexion est implémentée via Reflect et Proxy. Mais TypeScript ajoute sa propre couche — le typage statique, qui « disparaît » partiellement lors de la compilation. C'est là que les choses deviennent intéressantes.
Introspection des types : type guards et typeof
\n\nLa façon la plus simple de regarder à l'intérieur du code est d'utiliser des type guards. Ce sont des constructions qui vérifient le type de données à l'exécution :
\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\nfunction processValue(value: unknown) {\n if (isString(value)) {\n // TypeScript sait que value est une chaîne\n console.log(value.toUpperCase());\n } else {\n console.log('Ce n\\'est pas une chaîne');\n }\n}\n\nRemarquez value is string — c'est un prédicat de type. Il indique à TypeScript qu'après la vérification, la variable appartient à un type spécifique. C'est de l'introspection pure : nous observons le type, mais ne le modifions pas.
Réflexion via l'API Reflect
\n\nL'objet Reflect est un mécanisme de réflexion intégré en JavaScript. Il permet d'effectuer des actions qui étaient auparavant réalisées par des méthodes « magiques », mais désormais unifiées. Voici quelques exemples :
const user = {\n name: 'Alexis',\n age: 30,\n};\n\n// Introspection — obtenir la liste des clés\nconsole.log(Reflect.ownKeys(user)); // ['name', 'age']\n\n// Réflexion — lire dynamiquement une propriété\nconst propertyName = 'name';\nconsole.log(Reflect.get(user, propertyName)); // 'Alexis'\n\n// Réflexion — définir dynamiquement une propriété\nReflect.set(user, 'name', 'Marie');\nconsole.log(user.name); // 'Marie'\n\nEn quoi Reflect est-il meilleur que l'ancien user[prop] ? Premièrement, il retourne un boolean pour les opérations de définition, ce qui est pratique pour les vérifications. Deuxièmement, il fonctionne correctement avec les proxies et les descripteurs de propriétés. Pour TypeScript, c'est particulièrement important car la réflexion permet de contourner le typage statique lorsque c'est vraiment nécessaire.
Décorateurs : la réflexion sous stéroïdes
\n\nLes décorateurs sont sans doute l'exemple le plus frappant de réflexion en TypeScript. Ils permettent d'annoter des classes, méthodes, propriétés et paramètres, puis de modifier leur comportement. Depuis TypeScript 5.0, les décorateurs sont devenus standard (stage 3).
\n\nVoici à quoi ressemble un décorateur simple pour journaliser l'appel d'une méthode :
\n\nfunction logMethod(\n target: any,\n propertyKey: string,\n descriptor: PropertyDescriptor\n) {\n const originalMethod = descriptor.valu