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 Background Title How Index Types and keyof Work The keyof Operator The keyof Operator type Keys = keyof User // "id" | "name" | "email" Arrow Index Access Types Index Access Types type Value = User["name"] // string Arrow Mapped Types Mapped Types type Partial<T> {[P in keyof T]?: T[P]} Lower half: Use cases Use Cases for Index Types Scenario 1 Dynamic property access Scenario 2 Type-safe object operations Scenario 3 Create generic utility types Arrow marker

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

// Definiere den Benutzertyp
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

// Definiere den Benutzertyp
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

// Definiere den Benutzertyp
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

// Definiere einen Konfigurationstyp
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

// Einen gemischten Typ definieren
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

// Tupeltyp definieren
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

Empfehlung:Wenn du Objekteigenschaften manipulieren musst, verwende vorzugsweise Index-Typen, um die Typsicherheit zu wahren.

Weitere Erweiterungen