TypeScript Mapped Types
Mapped Types sind ein leistungsstarkes Feature in TypeScript, um neue Typen auf Basis bestehender Typen zu erstellen.
Sie ermöglichen Entwicklern, Eigenschaftsmerkmale stapelweise zu ändern, z. B. alle Eigenschaften optional oder alle Eigenschaften readonly zu machen.
Mapped Types sind die Kerntechnologie zur Implementierung der in TypeScript integrierten Utility-Typen.
SVG-Diagramm: Wie Mapped Types funktionieren
Warum Mapped Types benötigt werden
In der praktischen TypeScript-Entwicklung müssen wir oft Varianten auf Basis bestehender Typen erstellen.
Zum Beispiel eine Version, in der alle Eigenschaften optional sind, oder eine Version, in der alle Eigenschaften readonly sind.
Der traditionelle Weg besteht darin, diese Typen manuell zu definieren, was mühsam und fehleranfällig ist.
Mapped Types bieten eine deklarative Möglichkeit, diese Typvarianten automatisch zu generieren.
Konzepterklärung:Mapped Types verwenden
keyofundindas Schlüsselwort, um über alle Schlüssel eines vorhandenen Typs zu iterieren und dann dieselbe Typumwandlung auf jeden Schlüssel anzuwenden.
Grundlegende gemappte Typen
Grundlegende gemappte Typen erstellen neue Typen, indem sie über alle Schlüssel des ursprünglichen Typs iterieren.
Dies ist die Grundlage für die Implementierung von Utility-Typen wie Partial und Readonly.
Beispiel
interface User {
// Benutzer-ID
id: number;
// Benutzername
name: string;
// Benutzer-E-Mail
email: string;
}
// Den Utility-Typ Partial implementieren
// Über alle Schlüssel von T iterieren, optionalen Modifikator ? hinzufügen
type Partial<T> = {
// P iteriert über alle von keyof T zurückgegebenen Schlüssel
// T[P] ermittelt den Werttyp des entsprechenden Schlüssels im ursprünglichen Typ
[P in keyof T]?: T[P];
};
// Den Typ Partial verwenden
type PartialUser = Partial<User>;
// Der Typ PartialUser ist äquivalent zu:
// { id?: number; name?: string; email?: string }
// Es können nur partielle Eigenschaften bereitgestellt werden
var user: PartialUser = { name: "Alice" };
console.log("Partieller Benutzer: " + JSON.stringify(user));
Ausgabe:
部分用户: {"name":"Alice"}
Syntaxerklärung:
[P in keyof T]Gibt an, dass über alle Schlüssel vom Typ T iteriert wird.?Der Modifizierer macht Eigenschaften optional.
Eigenschaftsmodifizierer
Zugeordnete Typen unterstützen mehrere Eigenschaftsmodifizierer, um Eigenschaftsmerkmale zu ändern.
Diese Modifizierer können kombiniert werden, um unterschiedliche Anforderungen an die Typentransformation zu erfüllen.
Beispiel
interface User {
// Benutzername
name: string;
// Benutzeralter
age: number;
}
// Readonly-Mapping verwenden: Alle Eigenschaften auf readonly setzen
// Readonly-Modifizierer hinzufügen
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Optionales Mapping verwenden: alle Eigenschaften optional machen (Basisversion)
type Optional<T> = {
[P in keyof T]?: T[P];
};
// -? Mapping verwenden: den optionalen Modifikator entfernen (erforderlich machen)
// -? entfernt den ursprünglichen ?-Modifikator
type Required<T> = {
[P in keyof T]-?: T[P];
};
// readonly-Typ testen
var readonlyUser: Readonly<User> = { name: "Alice", age: 25 };
// readonlyUser.age = 30; // Fehler: readonly-Eigenschaft kann nicht geändert werden
// Optionalen Typ testen
var optionalUser: Optional<User> = { name: "Bob" };
console.log("Schreibgeschützt: " + JSON.stringify(readonlyUser));
console.log("Optional: " + JSON.stringify(optionalUser));
Ausgabe:
只读: {"name":"Alice","age":25}
可选: {"name":"Bob"}
Modifikator-Beschreibung:
?fügt optional hinzu,-?entfernt optional;readonlyfügt readonly hinzu,-readonlyentfernt readonly.
Schlüssel-Neuzuordnung
Zugeordnete Typen können außerdemasdas Schlüsselwort zum Neuzuordnen von Schlüsselnamen verwenden.
Dies ist nützlich, wenn Sie Schlüsselnamenformate einheitlich ändern müssen.
Beispiel
interface User {
// Benutzer-ID
id: number;
// Benutzername
name: string;
// Benutzeralter
age: number;
}
// Verwenden Sie das Schlüsselwort as, um Schlüsselnamen neu zuzuordnen
// Fügen Sie allen Schlüsseln ein Präfix hinzu
type WithPrefix<T, Prefix extends string> = {
// Verwende Template-Literal-Typen, um Schlüssel umzubenennen.
// Capitalize macht den ersten Buchstaben groß.
[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];
};
// Verwende WithPrefix, um das Präfix "user" hinzuzufügen.
type PrefixedUser = WithPrefix<User, "user">;
// Konvertierter Typ:
// { userId: number; userName: string; userAge: number }
// Verwende den Typ mit Präfix.
var user: PrefixedUser = { userId: 1, userName: "Alice", userAge: 25 };
console.log("Mit Präfix: " + JSON.stringify(user));
Ausgabe:
带前缀: {"userId":1,"userName":"Alice","userAge":25}
Template-Literal-Typen:Verwenden
`${Prefix}${Capitalizekann dynamisch neue Schlüsselnamen generieren.}`
Schlüsselfilterung
Durch die Kombination von bedingten Typen und gemappten Typen kann eine Schlüsselfilterung erreicht werden.
Dies ist sehr nützlich bei der Implementierung von Utility-Typen wie Omit.
Beispiel
interface User {
// Benutzer-ID
id: number;
// Benutzername
name: string;
// Benutzerpasswort
password: string;
// Benutzer-E-Mail
email: string;
}
// Omit implementieren: angegebene Schlüssel ausschließen
// Bedingte Typen verwenden, um Schlüssel zu filtern
type Omit<T, K extends keyof T> = {
// P iteriert über alle Schlüssel von T
// Wenn P K zuweisbar ist (d. h. in der Ausschlussliste steht), never zurückgeben (ausgeschlossen)
// Andernfalls P zurückgeben (Schlüssel behalten)
[P in keyof T as P extends K ? never : P]: T[P];
};
// Verwende Omit, um den Schlüssel password auszuschließen
type UserWithoutPassword = Omit<User, "password">;
// Konvertierter Typ:
// { id: number; name: string; email: string }
// Verwende den Typ nach dem Ausschließen von password
var user: UserWithoutPassword = { id: 1, name: "Alice", email: "[email protected]" };
console.log("Without password: " + JSON.stringify(user));
Ausgabe:
无密码: {"id":1,"name":"Alice","email":"[email protected]"}
never-Typ:In zugeordneten Typen führt die Verwendung von never als Eigenschaftstyp dazu, dass die Eigenschaft vollständig entfernt wird.
Bedingte Zuordnung
Zugeordnete Typen können mit bedingten Typen kombiniert werden, um je nach Eigenschaftstyp unterschiedliche Transformationen anzuwenden.
Dies macht die Typentransformation flexibler und intelligenter.
Beispiel
interface APIResponse {
// Antwortdaten
data: string;
// Fehlermeldung
error: string;
// Ob geladen wird
isLoading: boolean;
// Zeitstempel
timestamp: number;
}
// Funktionstypen in () => void umwandeln
// Über alle Eigenschaften iterieren und basierend auf dem Eigenschaftstyp eine bedingte Konvertierung durchführen
type FunctionToVoid<T> = {
// Wenn T[P] ein Funktionstyp ist, ihn in () => void umwandeln
// Andernfalls den ursprünglichen Typ unverändert lassen
[P in keyof T]: T[P] extends (...args: any[]) => any
? () => void
: T[P];
};
// Bedingte Zuordnung verwenden
var response: FunctionToVoid<APIResponse> = {
data: "hello",
error: "",
isLoading: false,
timestamp: Date.now()
};
console.log("Response: " + JSON.stringify(response));
Ausgabe:
响应: {"data":"hello","error":"","isLoading":false,"timestamp":...}
Anwendungsszenarien:Bedingte Zuordnung wird häufig in Szenarien verwendet, in denen je nach Typ unterschiedlich verfahren werden muss, z. B. bei der Verarbeitung von API-Antworten oder beim Bereinigen von Konfigurationsobjekten.
Integrierte Mapped Types
TypeScript verfügt über viele integrierte Utility-Typen, die auf Mapped Types basieren.
Diese Utility-Typen decken die meisten Anforderungen im täglichen Entwicklungsalltag ab.
Beispiel
type P1 = Partial<{ a: string; b: number }>;
// Ergebnis: { a?: string; b?: number }
// Required - macht alle optionalen Eigenschaften erforderlich
type R1 = Required<{ a?: string; b?: number }>;
// Ergebnis: { a: string; b: number }
// Readonly - macht alle Eigenschaften schreibgeschützt
type RO1 = Readonly<{ a: string; b: number }>;
// Ergebnis: { readonly a: string; readonly b: number }
// Pick - wählt angegebene Eigenschaften aus
type PK = Pick<{ a: string; b: number; c: boolesch }, "a" | "b">;
// Ergebnis: { a: string; b: number }
// Omit - schließt angegebene Eigenschaften aus
type OM = Omit<{ a: string; b: number; c: boolesch }, "c">;
// Ergebnis: { a: string; b: number }
// Test Partial
console.log("Partial: " + JSON.stringify({} as P1));
// Test Pick
console.log("Pick: " + JSON.stringify({ a: "x" } as PK));
Kombinationen von Utility-Typen:Diese eingebauten Utility-Typen sind alle auf Basis von Mapped Types und Conditional Types implementiert. Zu verstehen, wie sie funktionieren, kann dir helfen, sie besser zu verwenden.
Hinweise
- keyof-Schlüsselwort:Wird verwendet, um einen Union-Typ zu erhalten, der aus allen Schlüsseln eines Typs besteht.
- in-Schlüsselwort:Wird verwendet, um über Union-Typen von Schlüsselnamen zu iterieren.
- Position des Modifikators:
?undreadonlyVor dem Eigenschaftsnamen zeigt es das Hinzufügen eines Modifikators an. - Minus-Modifikator:
-?und-readonlyWird verwendet, um Modifikatoren zu entfernen. - as-Schlüsselwort:Wird verwendet, um Schlüsselnamen neu zuzuordnen; muss einen String- oder numerischen Literaltyp zurückgeben.
Fortgeschritten:Mapped Types können mit bedingten Typen und Template-Literal-Typen kombiniert werden, um komplexe Typumwandlungen umzusetzen.
Zusammenfassung
Mapped Types sind eines der leistungsstärksten Features im TypeScript-Typsystem.
- keyof:Alle Schlüssel eines Typs abrufen
- in:Über Schlüsselnamen für die Zuordnung iterieren
?:Optionale Modifikatoren hinzufügen- readonly:readonly-Modifikatoren hinzufügen
- -?:Optionale Modifikatoren entfernen
- as:Schlüsselnamen neu zuordnen
Best Practices:Der kluge Einsatz von Mapped Types kann wiederholte Typdefinitionen stark reduzieren und die Wartbarkeit des Codes verbessern.
Weitere Erweiterungen