Zum Inhalt springen
TypeScript Anfänger 25 min

Union & Intersection Types

Lerne wie du mit Union und Intersection Types flexible und präzise Typen in TypeScript erstellst.

Aktualisiert:

Union & Intersection Types

Union und Intersection Types sind mächtige Features, die es dir ermöglichen, Typen zu kombinieren. Sie sind fundamental für flexibles und gleichzeitig typsicheres Programmieren.

Union Types

Ein Union Type bedeutet “entweder A ODER B”:

// String oder Number
let id: string | number;

id = "abc123";  // OK
id = 42;        // OK
id = true;      // Fehler!

Praktische Verwendung

// Funktion, die verschiedene Typen akzeptiert
function formatId(id: string | number): string {
    if (typeof id === "string") {
        return id.toUpperCase();
    }
    return `ID-${id}`;
}

console.log(formatId("abc"));  // "ABC"
console.log(formatId(123));    // "ID-123"

Union mit Literal Types

Besonders mächtig mit Literal Types:

type Status = "pending" | "approved" | "rejected";

let orderStatus: Status;
orderStatus = "pending";   // OK
orderStatus = "shipped";   // Fehler!

// Funktion mit Union Literal
function handleStatus(status: Status): string {
    switch (status) {
        case "pending":
            return "In Bearbeitung...";
        case "approved":
            return "Genehmigt!";
        case "rejected":
            return "Abgelehnt.";
    }
}

Discriminated Unions

Ein Pattern für Objekte mit gemeinsamer Eigenschaft:

type Success = {
    type: "success";
    data: string;
};

type Error = {
    type: "error";
    message: string;
};

type Result = Success | Error;

function handleResult(result: Result): void {
    // TypeScript weiß durch 'type', welche Properties verfügbar sind
    if (result.type === "success") {
        console.log("Daten:", result.data);  // OK - data existiert
    } else {
        console.log("Fehler:", result.message);  // OK - message existiert
    }
}

Union mit Arrays

// Array, das Strings ODER Zahlen enthält
let mixedArray: (string | number)[] = [1, "zwei", 3, "vier"];

// Array von Strings ODER Array von Zahlen
let eitherArray: string[] | number[] = [1, 2, 3];
eitherArray = ["a", "b", "c"];
// eitherArray = [1, "zwei"];  // Fehler!

Intersection Types

Ein Intersection Type bedeutet “A UND B zusammen”:

type HasName = {
    name: string;
};

type HasAge = {
    age: number;
};

// Muss BEIDE Typen erfüllen
type Person = HasName & HasAge;

let person: Person = {
    name: "Max",
    age: 25
};

// Fehler - beide Properties sind required
let incomplete: Person = { name: "Max" };  // Fehler: 'age' fehlt

Typen erweitern

Intersection ist ideal zum Erweitern von Typen:

type BaseUser = {
    id: number;
    email: string;
};

type WithTimestamps = {
    createdAt: Date;
    updatedAt: Date;
};

type UserWithTimestamps = BaseUser & WithTimestamps;

let user: UserWithTimestamps = {
    id: 1,
    email: "max@example.com",
    createdAt: new Date(),
    updatedAt: new Date()
};

Mehrfache Intersections

type Nameable = { name: string };
type Ageable = { age: number };
type Emailable = { email: string };

type FullPerson = Nameable & Ageable & Emailable;

let person: FullPerson = {
    name: "Max",
    age: 25,
    email: "max@example.com"
};

Union vs Intersection

type A = { a: string };
type B = { b: number };

// Union: A ODER B
type UnionAB = A | B;
let union1: UnionAB = { a: "hello" };      // OK
let union2: UnionAB = { b: 42 };           // OK
let union3: UnionAB = { a: "hi", b: 42 };  // OK (beide erfüllt)

// Intersection: A UND B
type IntersectionAB = A & B;
let inter: IntersectionAB = { a: "hello", b: 42 };  // Muss beide haben

Type Narrowing

Bei Union Types muss TypeScript wissen, mit welchem Typ du arbeitest:

typeof Guard

function process(value: string | number): void {
    if (typeof value === "string") {
        // Hier weiß TypeScript: value ist string
        console.log(value.toUpperCase());
    } else {
        // Hier weiß TypeScript: value ist number
        console.log(value.toFixed(2));
    }
}

in Operator

type Bird = { fly: () => void };
type Fish = { swim: () => void };

function move(animal: Bird | Fish): void {
    if ("fly" in animal) {
        animal.fly();
    } else {
        animal.swim();
    }
}

instanceof

function formatDate(date: Date | string): string {
    if (date instanceof Date) {
        return date.toISOString();
    }
    return new Date(date).toISOString();
}

Custom Type Guards

type Success = { type: "success"; data: string };
type Failure = { type: "failure"; error: Error };
type Result = Success | Failure;

// Custom Type Guard
function isSuccess(result: Result): result is Success {
    return result.type === "success";
}

function handleResult(result: Result): void {
    if (isSuccess(result)) {
        console.log(result.data);  // TypeScript weiß: result ist Success
    } else {
        console.log(result.error);  // TypeScript weiß: result ist Failure
    }
}

Praktische Beispiele

API Response Handler

type LoadingState = {
    status: "loading";
};

type SuccessState<T> = {
    status: "success";
    data: T;
};

type ErrorState = {
    status: "error";
    error: string;
};

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

function renderState<T>(state: AsyncState<T>): string {
    switch (state.status) {
        case "loading":
            return "Laden...";
        case "success":
            return `Daten: ${JSON.stringify(state.data)}`;
        case "error":
            return `Fehler: ${state.error}`;
    }
}

Event Handler

type ClickEvent = {
    type: "click";
    x: number;
    y: number;
};

type KeyEvent = {
    type: "key";
    key: string;
    code: number;
};

type ScrollEvent = {
    type: "scroll";
    scrollTop: number;
};

type AppEvent = ClickEvent | KeyEvent | ScrollEvent;

function handleEvent(event: AppEvent): void {
    switch (event.type) {
        case "click":
            console.log(`Klick bei ${event.x}, ${event.y}`);
            break;
        case "key":
            console.log(`Taste ${event.key} gedrückt`);
            break;
        case "scroll":
            console.log(`Gescrollt zu ${event.scrollTop}`);
            break;
    }
}

Mixins mit Intersection

type Printable = {
    print: () => void;
};

type Serializable = {
    toJSON: () => string;
};

type Loggable = {
    log: (message: string) => void;
};

// Kombiniere alle Features
type FullFeature = Printable & Serializable & Loggable;

function createLogger(): FullFeature {
    return {
        print() { console.log("Printing..."); },
        toJSON() { return "{}"; },
        log(message) { console.log(message); }
    };
}

Zusammenfassung

Union Types (|):

  • Wert kann EINER der Typen sein
  • Gut für Variablen mit mehreren möglichen Typen
  • Erfordert Type Narrowing zur Verwendung

Intersection Types (&):

  • Wert muss ALLE Typen erfüllen
  • Gut zum Kombinieren/Erweitern von Typen
  • Alle Properties müssen vorhanden sein

Type Narrowing:

  • typeof für primitive Typen
  • in für Property-Checks
  • instanceof für Klassen
  • Custom Type Guards für komplexe Fälle

Im nächsten Modul lernst du, wie du Funktionen in TypeScript typisierst!

Zurück zum TypeScript Kurs