Warum dieses Thema Stack Overflow zum Explodieren bringt?
\n\nWenn Sie jemals in TypeScript programmiert und versucht haben, ein Objekt zu erstellen, bei dem alle Werte vom gleichen Typ sein sollen, sind Sie sicherlich auf dieses Problem gestoßen. Die Frage "Enforcing the type of the indexed members of a Typescript object" hat auf Stack Overflow tausende Aufrufe erhalten – nicht ohne Grund, denn es ist ein klassischer Stolperstein für Entwickler, die von JavaScript wechseln.
\n\nStellen Sie sich vor: Sie haben ein Cache-Wörterbuch, eine Konfiguration mit dynamischen Schlüsseln oder einfach eine Map mit Zahlen. Sie möchten, dass TypeScript überwacht, dass jeder Wert im Objekt ein String, eine Zahl oder ein komplexer Typ ist. Aber der Compiler lässt Fehler trotzdem durch. Kommt Ihnen das bekannt vor?
\n\nIn diesem Artikel werden wir alle grundlegenden Ansätze zur Lösung dieses Problems durchgehen: von einfachen Index Signatures bis zu fortgeschrittenen Mapped Types. Sie erfahren, wann Sie welche Methode verwenden und welche Fallstricke auf Sie warten.
Problem: Objekt mit dynamischen Schlüsseln
\n\nAngenommen, wir schreiben ein Benutzerverwaltungssystem. Wir haben ein Objekt, bei dem die Schlüssel die Benutzer-IDs und die Werte die Namen der Benutzer sind:
\n\ninterface UserCache {\n [userId: string]: string;\n}\n\nconst cache: UserCache = {\n 'user_1': 'Alice',\n 'user_2': 'Bob',\n // 'user_3': 123; // Fehler! Muss ein String sein\n};\n\ncache['user_4'] = 'Charlie'; // OK\ncache['user_5'] = 42; // Fehler! Type 'number' ist nicht zuweisbar zu 'string'\n\n\nEs scheint zu funktionieren. Aber es gibt einen Haken: Die Index Signature erlaubt beliebige String-Schlüssel. Was aber, wenn wir die Menge der zulässigen Schlüssel einschränken möchten?
Ansatz 1: Index Signature – der Klassiker
\n\nIndex Signature ist der einfachste Weg, den Typ für alle Werte eines Objekts festzulegen. Die Syntax:
\n\ninterface StringMap {\n [key: string]: string;\n}\n\nconst map: StringMap = {\n hello: 'world',\n foo: 'bar',\n};\n\n\nVorteile:
\n- \n
- Einfachheit und Lesbarkeit \n
- Funktioniert mit beliebig vielen Schlüsseln \n
Nachteile:
\n- \n
- Schlüsselmenge kann nicht eingeschränkt werden \n
- Alle Werte müssen vom gleichen Typ sein (oder ein Union-Typ) \n
- Funktioniert nicht mit
undefined-Werten (Vorsicht ist geboten) \n
Ein häufiger Anfängerfehler: Der Versuch, einem solchen Objekt eine Methode hinzuzufügen:
\n\ninterface BadMap {\n [key: string]: string;\n length: number; // Fehler! 'number' ist nicht zuweisbar zu 'string'\n}\n\n\nDas funktioniert nicht, weil length den allgemeinen Typ verletzt. Wenn Sie Typen mischen müssen, verwenden Sie Union-Typen oder Mapped Types.
Ansatz 2: Record – eingebauter Helfer
\n\nTypeScript bietet den eingebauten Typ Record<K, V>, der dasselbe tut, aber mit einer prägnanteren Syntax:
type StringRecord = Record<string, string>;\n\nconst config: StringRecord = {\n apiUrl: 'https://api.example.com',\n timeout: '5000', // OK, String\n // port: 8080, // Fehler!\n};\n\n// Schlüssel können eingeschränkt werden:\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', // Fehler! 'age' ist nicht in AllowedKeys enthalten\n};\n\n\nWann verwenden:
\n- \n
- Wenn Sie schnell ein typisiertes Wörterbuch erstellen möchten \n
- Wenn die Schlüssel eine bestimmte Menge von Werten sind (Union-Typ) \n
- Für Konfigurationen mit bekannten Feldern \n
Nachteil: Record erlaubt es nicht, unterschiedliche Typen für verschiedene Schlüssel festzulegen. Dafür gibt es Mapped Types.
Ansatz 3: Map – Alternative aus ES6
\n\nWenn Sie die Map aus JavaScript verwenden, typisiert TypeScript sie hervorragend