TypeScript Index-Typen und das keyof-Schlüsselwort
Index-Typen und keyof sind leistungsstarke Werkzeuge in TypeScript zur Manipulation von Objekttypen.
Sie ermöglichen uns, dynamisch auf Objekteigenschaften zuzugreifen und flexible Typzuordnungen zu erstellen.
SVG-Diagramm: Wie Index-Typen und keyof funktionieren
Warum brauchen wir Index-Typen?
In JavaScript müssen wir häufig dynamisch auf Objekteigenschaften zugreifen.
Zum Beispiel muss eine Funktion möglicherweise alle Schlüssel eines Objekts abrufen oder anhand eines Schlüssels auf den entsprechenden Werttyp zugreifen.
Index-Typen und keyof ermöglichen es uns, diese Dynamik im Typsystem auszudrücken und dabei die Typsicherheit zu wahren.
Konzept:Index-Typen sind eine Klasse von Typen, die dynamischen Zugriff auf Objekteigenschaften ermöglichen, und keyof wird verwendet, um einen Union-Typ zu erhalten, der aus allen Schlüsseln eines Objekttyps besteht.
Der keyof-Operator
Der keyof-Operator
Beispiel
interface User {
id: number; // Benutzer-ID
name: string; // Benutzername
email: string; // E-Mail
age?: number; // Alter (optional)
}
// Verwende keyof, um den Union-Typ aller Schlüssel zu erhalten
// Ergebnis: "id" | "name" | "email" | "age"
type UserKeys = keyof User;
// keyof testen
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = {
id: 1,
name: "Alice",
email: "[email protected]"
};
// Die name-Eigenschaft abrufen
const userName: string = getProperty(user, "name");
console.log("Benutzername: " + userName);
Ausgabe:
用户名: Alice
Erklärung:keyof gibt einen Union-Typ aus Schlüsselnamen-Literalen zurück, nicht einen string-Typ.
Indexzugriffstypen
Mit Indexzugriffstypen können Sie den Typ einer Objekteigenschaft abrufen.
Beispiel
interface User {
id: number;
name: string;
email: string;
}
// Verwenden Sie Indexzugriffstypen, um Eigenschaftstypen abzurufen
type UserId = User["id"]; // number
type UserName = User["name"]; // string
// Mit einem Union-Typ können Sie auf mehrere Schlüssel zugreifen
type UserIdAndName = User["id" | "name"]; // number | string
// Verwenden Sie keyof, um den Union-Typ aller Eigenschaftstypen zu erhalten
type AllUserValues = User[keyof User]; // number | string
// Praktisches Anwendungsbeispiel
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: "Bob", email: "[email protected]" };
// Typ von id abrufen
const idValue: number = getValue(user, "id");
console.log("ID: " + idValue);
// Typ von name abrufen
const nameValue: string = getValue(user, "name");
console.log("Name: " + nameValue);
Indexzugriff:Mit eckigen Klammern [] kannst du auf den Typ einer bestimmten Eigenschaft eines Objekttyps zugreifen; das ist sehr mächtig und flexibel.
Grundlagen zu Mapped Types
Mapped Types ermöglichen es dir, neue Typen auf Basis vorhandener Typen zu erstellen, indem du über alle ihre Schlüssel iterierst.
Beispiel
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Alle Eigenschaften optional machen
type PartialUser = Partial<User>;
// Alle Eigenschaften schreibgeschützt machen
type ReadonlyUser = Readonly<User>;
// Benutzerdefinierter Mapped Type: Alle Eigenschaften optional machen und sie in Strings umwandeln
type Stringify<T> = {
[P in keyof T]: string;
};
type StringifiedUser = Stringify<User>;
// Praktisches Anwendungsbeispiel
const partialUser: PartialUser = {
id: 1,
name: "Alice"
// email und age sind optional
};
const readonlyUser: ReadonlyUser = {
id: 1,
name: "Bob",
email: "[email protected]",
age: 25
};
// readonlyUser.name = "Charlie"; // Fehler: readonly
console.log("Partieller Benutzer: " + partialUser.name);
console.log("Schreibgeschützter Benutzer: " + readonlyUser.name);
Zugeordnete Typen:Die Verwendung der Syntax [P in keyof T], um über alle Schlüssel eines Typs zu iterieren, ist die Grundlage für die Erstellung von Utility-Typen.
Einschränkung des Typs von Schlüsseln
Verwende keyof und generische Einschränkungen, um die Schlüssel einzuschränken, die eine Funktion akzeptiert.
Beispiel
interface Config {
apiUrl: string;
timeout: number;
retry: boolean;
}
// Es können nur vorhandene Schlüssel abgerufen werden
function getConfigValue<T, K extends keyof T>(
config: T,
key: K
): T[K] {
return config[key];
}
// Die Konfiguration definieren
const config: Config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retry: true
};
// Korrekt: Der Schlüssel existiert
const url: string = getConfigValue(config, "apiUrl");
const timeoutVal: number = getConfigValue(config, "timeout");
// Fehler: Der Schlüssel existiert nicht (TypeScript wird einen Fehler melden)
// const invalid = getConfigValue(config, "unknown");
console.log("API-URL: " + url);
console.log("Zeitüberschreitung: " + timeoutVal);
Einschränkung:K extends keyof T stellt sicher, dass der übergebene Schlüssel ein Schlüssel sein muss, der auf dem Objekt existiert, wodurch Laufzeitfehler verhindert werden.
Nur Eigenschaften eines bestimmten Typs abrufen
Eigenschaftsschlüssel eines bestimmten Typs aus einem Objekttyp extrahieren.
Beispiel
interface Mixed {
id: number;
name: string;
age: number;
email: string;
active: boolean;
}
// Alle Schlüssel des String-Typs extrahieren
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
// Alle Schlüssel des Typs number extrahieren
type NumberKeys<T> = {
[K in keyof T]: T[K] extends number ? K : never;
}[keyof T];
// Test
type StringProps = StringKeys<Mixed>; // "name" | "email"
type NumberProps = NumberKeys<Mixed>; // "id" | "age"
// Praktische Anwendung: den Wert einer String-Eigenschaft abrufen
function getStringProps<T, K extends StringKeys<T>>(
obj: T,
keys: K[]
): T[K][] {
return keys.map(key => obj[key]);
}
const mixed: Mixed = {
id: 1,
name: "Alice",
age: 25,
email: "[email protected]",
active: true
};
const strings = getStringProps(mixed, ["name", "email"]);
console.log("String-Eigenschaften: " + strings.join(", "));
Bedingte Typen:Die Kombination von bedingten Typen und Mapped Types ermöglicht komplexe Typfilterung und -extraktion.
Iteration über Array-Typen
Verwenden Sie Indextypen, um Arrays und Tupel zu manipulieren.
Beispiel
type Tuple = [string, number, boolean];
// Tupel-Elementtyp abrufen
type First = Tuple[0]; // string
type Second = Tuple[1]; // number
type Third = Tuple[2]; // boolean
// number verwenden, um den Typ aller Elemente abzurufen
type AllElements = Tuple[number]; // string | number | boolean
// Typ des Array-Elements ermitteln
type StringArray = string[];
type ArrayElement = StringArray[number]; // string
// Praktische Anwendung: Funktionsüberladungen
function getElement<T extends any[]>(
arr: T,
index: number
): T[indexof T] | undefined {
return index < arr.length ? arr[index] : undefined;
}
const tuple: Tuple = ["hello", 123, true];
const arr: string[] = ["a", "b", "c"];
console.log("Tupel-Element + getElement(tuple, 0));
console.log("Array-Element + getElement(arr, 1));
Tupel-Indizierung:Tupel können numerische Indizes verwenden, und jeder Index entspricht einem bestimmten Elementtyp, was Arrays nicht können.
Hinweise
- keyof gibt einen Union-Typ zurück:keyof gibt einen Union-Typ aus Schlüsselnamen-Literalen zurück
- Sicherheit beim Indexzugriff:Stelle sicher, dass der zugegriffene Schlüssel im Zieltyp existiert
- Generische Einschränkungen:Verwende extends keyof, um generische Parameter einzuschränken
- Mapped Types erfordern das Schlüsselwort in:Mapped Types verwenden die Syntax [P in keyof T]
Best Practices:Index-Typen sind der Grundpfeiler für die Erstellung generischer Utility-Typen; ihre Beherrschung kann deine Fähigkeiten zur Typmanipulation erheblich verbessern.
Zusammenfassung
Index-Typen und keyof sind wichtige Bestandteile des TypeScript-Typsystems.
- keyof:Alle Schlüssel eines Objekttyps abrufen
- Indexzugriff:Eigenschaftstypen über Schlüssel abrufen
- Mapped Types:Neue Typen auf Basis vorhandener Typen erstellen
- Typsicherheit:Typsicherheit für dynamischen Eigenschaftszugriff sicherstellen
Weitere ErweiterungenEmpfehlung:Wenn du Objekteigenschaften manipulieren musst, verwende vorzugsweise Index-Typen, um die Typsicherheit zu wahren.