Reflexion und Introspektion in TypeScript: Wie Code sich selbst betrachtet
\n\nStellen Sie sich ein Programm vor, das sich selbst zur Laufzeit analysieren kann. Es kann herausfinden, welche Typen seine Variablen haben, welche Eigenschaften Objekte besitzen und sogar Entscheidungen auf Basis dieser Informationen treffen. Klingt nach Magie? Tatsächlich handelt es sich um Reflexion und Introspektion – mächtige Techniken der Metaprogrammierung, die auch in TypeScript verfügbar sind.
\n\nIn diesem Artikel klären wir, worin sich Reflexion und Introspektion unterscheiden, wie man sie in TypeScript verwendet und warum sie in realen Projekten nützlich sein können. Keine langweilige Theorie – nur lebendiger Code und praktische Beispiele.
\n\nWas sind Introspektion und Reflexion?
\n\nLassen Sie uns gleich Klarheit schaffen. In der Programmierung werden diese Begriffe oft verwechselt, aber es gibt einen Unterschied:
\n\n- \n
- Introspektion – die Fähigkeit eines Programms, seine eigene Struktur zu beobachten. Sie können herausfinden, welche Eigenschaften ein Objekt hat, welchen Typ es besitzt, aber Sie können sie nicht ändern. \n
- Reflexion – eine fortgeschrittenere Stufe: Das Programm betrachtet sich nicht nur selbst, sondern ändert auch sein Verhalten zur Laufzeit. Beispielsweise ruft es dynamisch Methoden auf oder erstellt neue Instanzen von Klassen. \n
In JavaScript, auf dem TypeScript basiert, wird Reflexion durch Reflect und Proxy realisiert. TypeScript fügt jedoch eine eigene Ebene hinzu – die statische Typisierung, die bei der Kompilierung teilweise „verschwindet“. Genau hier wird es interessant.
Typ-Introspektion: Type Guards und typeof
\n\nDer einfachste Weg, in den Code hineinzuschauen, ist die Verwendung von Type Guards. Dies sind Konstrukte, die den Datentyp zur Laufzeit überprüfen:
\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 weiß, dass value ein String ist\n console.log(value.toUpperCase());\n } else {\n console.log('Das ist kein String');\n }\n}\n\nBeachten Sie value is string – das ist ein Typ-Prädikat. Es teilt TypeScript mit, dass die Variable nach der Prüfung zu einem bestimmten Typ gehört. Dies ist reine Introspektion: Wir betrachten den Typ, ändern ihn aber nicht.
Reflexion mit der Reflect API
\n\nDas Reflect-Objekt ist ein eingebauter Mechanismus für Reflexion in JavaScript. Es ermöglicht Aktionen, die früher mit „magischen“ Methoden durchgeführt wurden, jetzt aber vereinheitlicht sind. Hier einige Beispiele:
const user = {\n name: 'Alexej',\n age: 30,\n};\n\n// Introspektion – Liste der Schlüssel abrufen\nconsole.log(Reflect.ownKeys(user)); // ['name', 'age']\n\n// Reflexion – Eigenschaft dynamisch lesen\nconst propertyName = 'name';\nconsole.log(Reflect.get(user, propertyName)); // 'Alexej'\n\n// Reflexion – Eigenschaft dynamisch setzen\nReflect.set(user, 'name', 'Maria');\nconsole.log(user.name); // 'Maria'\n\nWarum ist Reflect besser als das altbekannte user[prop]? Erstens gibt es für Set-Operationen einen boolean-Wert zurück, was für Überprüfungen praktisch ist. Zweitens funktioniert es korrekt mit Proxies und Property-Deskriptoren. Für TypeScript ist dies besonders wichtig, da Reflexion es ermöglicht, die statische Typisierung zu umgehen, wenn es wirklich nötig ist.
Decorators: Reflexion auf Steroiden
\n\nDecorators sind wohl das bekannteste Beispiel für Reflexion in TypeScript. Sie ermöglichen es, Klassen, Methoden, Eigenschaften und Parameter zu annotieren und dann ihr Verhalten zu ändern. Seit TypeScript 5.0 sind Decorators standardisiert (Stage 3).
\n\nSo sieht ein einfacher Decorator zur Protokollierung eines Methodenaufrufs aus:
\n\nfunction logMethod(\n target: any,\n propertyKey: string,\n descriptor: PropertyDescriptor\n) {\n const originalMethod = descriptor.valu