Zum Inhalt springen
TypeScript Fortgeschritten 35 min

Mapped Types

Lerne Mapped Types in TypeScript für dynamische Typ-Transformationen.

Aktualisiert:

Mapped Types

Mapped Types ermöglichen es dir, bestehende Typen zu transformieren, indem du über ihre Properties iterierst. Sie sind eines der mächtigsten Features für fortgeschrittene Typisierung.

Grundlagen

Die Syntax basiert auf Index Signatures:

type MappedType = {
    [Key in UnionOfKeys]: ValueType;
};

Einfaches Beispiel

type Keys = "name" | "age" | "email";

// Alle Keys werden zu string
type StringRecord = {
    [K in Keys]: string;
};

// Ergebnis:
// {
//     name: string;
//     age: string;
//     email: string;
// }

keyof und Mapped Types

Mit keyof kannst du über die Keys eines bestehenden Typs iterieren:

type User = {
    id: number;
    name: string;
    email: string;
};

// Alle Properties werden zu boolean
type UserFlags = {
    [K in keyof User]: boolean;
};

// Ergebnis:
// {
//     id: boolean;
//     name: boolean;
//     email: boolean;
// }

Zugriff auf den Original-Typ

Mit T[K] greifst du auf den Original-Typ zu:

type User = {
    id: number;
    name: string;
    email: string;
};

// Kopiert den Typ (identity mapping)
type CopyOfUser = {
    [K in keyof User]: User[K];
};

// Macht alle Properties optional
type PartialUser = {
    [K in keyof User]?: User[K];
};

// Macht alle Properties readonly
type ReadonlyUser = {
    readonly [K in keyof User]: User[K];
};

Generische Mapped Types

Die wahre Kraft liegt in generischen Mapped Types:

// Partial - macht alle Properties optional
type Partial<T> = {
    [K in keyof T]?: T[K];
};

// Required - macht alle Properties required
type Required<T> = {
    [K in keyof T]-?: T[K];  // -? entfernt optional
};

// Readonly - macht alle Properties readonly
type Readonly<T> = {
    readonly [K in keyof T]: T[K];
};

// Mutable - entfernt readonly
type Mutable<T> = {
    -readonly [K in keyof T]: T[K];
};

Verwendung

type User = {
    id: number;
    name: string;
    email?: string;
};

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }

type RequiredUser = Required<User>;
// { id: number; name: string; email: string; }

type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email?: string; }

Pick und Omit

Wähle bestimmte Properties aus:

// Pick - nur bestimmte Keys behalten
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

// Omit - bestimmte Keys ausschließen
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// Verwendung
type User = {
    id: number;
    name: string;
    email: string;
    password: string;
};

type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string; }

type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; }

Record Type

Erstellt einen Typ mit bestimmten Keys und Value-Typ:

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

// Verwendung
type PageInfo = {
    title: string;
    url: string;
};

type Pages = "home" | "about" | "contact";

type Site = Record<Pages, PageInfo>;
// {
//     home: PageInfo;
//     about: PageInfo;
//     contact: PageInfo;
// }

const site: Site = {
    home: { title: "Home", url: "/" },
    about: { title: "About", url: "/about" },
    contact: { title: "Contact", url: "/contact" }
};

Key Remapping (as clause)

Transformiere Keys während des Mappings:

type User = {
    name: string;
    age: number;
    email: string;
};

// Prefix hinzufügen
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<User>;
// {
//     getName: () => string;
//     getAge: () => number;
//     getEmail: () => string;
// }

// Setters
type Setters<T> = {
    [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type UserSetters = Setters<User>;
// {
//     setName: (value: string) => void;
//     setAge: (value: number) => void;
//     setEmail: (value: string) => void;
// }

Keys filtern

// Nur Properties eines bestimmten Typs behalten
type OnlyStrings<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type User = {
    id: number;
    name: string;
    email: string;
    age: number;
};

type StringPropsOfUser = OnlyStrings<User>;
// { name: string; email: string; }

// Nur required Properties
type OnlyRequired<T> = {
    [K in keyof T as undefined extends T[K] ? never : K]: T[K];
};

Praktische Mapped Types

DeepPartial

type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object
        ? DeepPartial<T[K]>
        : T[K];
};

type Config = {
    database: {
        host: string;
        port: number;
        credentials: {
            username: string;
            password: string;
        };
    };
    server: {
        port: number;
    };
};

type PartialConfig = DeepPartial<Config>;
// Alle nested Properties sind optional

DeepReadonly

type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object
        ? DeepReadonly<T[K]>
        : T[K];
};

const config: DeepReadonly<Config> = {
    database: {
        host: "localhost",
        port: 5432,
        credentials: {
            username: "admin",
            password: "secret"
        }
    },
    server: { port: 3000 }
};

// config.database.port = 3306;  // Fehler: readonly

Nullable

type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

type User = {
    name: string;
    email: string;
};

type NullableUser = Nullable<User>;
// { name: string | null; email: string | null; }

Event Handlers

type Events = {
    click: { x: number; y: number };
    focus: { target: string };
    blur: { target: string };
};

type EventHandlers = {
    [K in keyof Events as `on${Capitalize<K>}`]: (event: Events[K]) => void;
};

// {
//     onClick: (event: { x: number; y: number }) => void;
//     onFocus: (event: { target: string }) => void;
//     onBlur: (event: { target: string }) => void;
// }

const handlers: EventHandlers = {
    onClick: (e) => console.log(`Click at ${e.x}, ${e.y}`),
    onFocus: (e) => console.log(`Focus on ${e.target}`),
    onBlur: (e) => console.log(`Blur from ${e.target}`)
};

Form State

type FormField<T> = {
    value: T;
    touched: boolean;
    error?: string;
};

type FormState<T> = {
    [K in keyof T]: FormField<T[K]>;
};

type UserForm = {
    name: string;
    email: string;
    age: number;
};

type UserFormState = FormState<UserForm>;
// {
//     name: FormField<string>;
//     email: FormField<string>;
//     age: FormField<number>;
// }

const formState: UserFormState = {
    name: { value: "", touched: false },
    email: { value: "", touched: false },
    age: { value: 0, touched: false }
};

Kombination mit Conditional Types

type NonFunctionProperties<T> = {
    [K in keyof T as T[K] extends Function ? never : K]: T[K];
};

type FunctionProperties<T> = {
    [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

type User = {
    id: number;
    name: string;
    greet: () => string;
    save: () => Promise<void>;
};

type UserData = NonFunctionProperties<User>;
// { id: number; name: string; }

type UserMethods = FunctionProperties<User>;
// { greet: () => string; save: () => Promise<void>; }

Zusammenfassung

Mapped TypeBeschreibung
Partial<T>Alle Properties optional
Required<T>Alle Properties required
Readonly<T>Alle Properties readonly
Pick<T, K>Nur bestimmte Properties
Omit<T, K>Properties ausschließen
Record<K, T>Objekt mit bestimmten Keys

Key Features:

  • keyof T für alle Keys eines Typs
  • T[K] für den Typ einer Property
  • as für Key-Transformation
  • +/- für Modifier hinzufügen/entfernen

Im nächsten Kapitel lernst du Conditional Types!

Zurück zum TypeScript Kurs