Zum Inhalt springen
TypeScript Fortgeschritten 25 min

Extending & Merging

Lerne wie du Interfaces erweitern und Typen kombinieren kannst in TypeScript.

Aktualisiert:

Extending & Merging

Das Erweitern und Kombinieren von Typen ist ein mächtiges Feature in TypeScript. Es ermöglicht dir, bestehende Typen wiederzuverwenden und zu spezialisieren.

Interface Extending

Einfaches Extending

interface Animal {
    name: string;
    age: number;
}

interface Dog extends Animal {
    breed: string;
    bark(): void;
}

const dog: Dog = {
    name: "Buddy",
    age: 3,
    breed: "Golden Retriever",
    bark() {
        console.log("Woof!");
    }
};

Mehrfaches Extending

interface Identifiable {
    id: number;
}

interface Timestamped {
    createdAt: Date;
    updatedAt: Date;
}

interface Nameable {
    name: string;
}

interface User extends Identifiable, Timestamped, Nameable {
    email: string;
    role: "admin" | "user";
}

const user: User = {
    id: 1,
    createdAt: new Date(),
    updatedAt: new Date(),
    name: "Max",
    email: "max@example.com",
    role: "admin"
};

Property Override

Du kannst Properties überschreiben (mit kompatiblem Typ):

interface Base {
    value: string | number;
}

interface Specific extends Base {
    value: number;  // Eingeschränkt auf number
}

const item: Specific = {
    value: 42  // Muss number sein
};

Type Intersection

Types werden mit & kombiniert:

type Identifiable = {
    id: number;
};

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

type User = Identifiable & Timestamped & {
    name: string;
    email: string;
};

const user: User = {
    id: 1,
    createdAt: new Date(),
    updatedAt: new Date(),
    name: "Max",
    email: "max@example.com"
};

Intersection mit Interfaces

Types und Interfaces können kombiniert werden:

interface Person {
    name: string;
    age: number;
}

type Employee = Person & {
    employeeId: string;
    department: string;
};

const employee: Employee = {
    name: "Max",
    age: 30,
    employeeId: "E001",
    department: "Engineering"
};

Declaration Merging

Nur Interfaces können gemergt werden:

// Erste Deklaration
interface Config {
    apiUrl: string;
}

// Zweite Deklaration (wird gemergt)
interface Config {
    timeout: number;
}

// Dritte Deklaration
interface Config {
    retries: number;
}

// Config hat jetzt alle Properties
const config: Config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3
};

Globale Interface-Erweiterung

Nützlich für Library-Erweiterungen:

// Erweitere Window
declare global {
    interface Window {
        analytics: {
            track: (event: string, data?: object) => void;
        };
    }
}

// Jetzt typsicher
window.analytics.track("page_view", { path: "/" });

Express Request erweitern

declare global {
    namespace Express {
        interface Request {
            user?: {
                id: number;
                email: string;
            };
        }
    }
}

// In Middleware
app.use((req, res, next) => {
    req.user = { id: 1, email: "test@example.com" };
    next();
});

Module Augmentation

Typen aus Modulen erweitern:

// original-module.ts
export interface Theme {
    primary: string;
    secondary: string;
}

// augmentation.ts
declare module "./original-module" {
    interface Theme {
        accent: string;
        background: string;
    }
}

// Jetzt hat Theme alle vier Properties

Utility Types für Extending

Pick - Properties auswählen

interface User {
    id: number;
    name: string;
    email: string;
    password: string;
    createdAt: Date;
}

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

Omit - Properties ausschließen

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

Partial - Alles optional

type UpdateUserInput = Partial<User>;
// Alle Properties sind optional

Required - Alles required

interface Config {
    apiUrl?: string;
    timeout?: number;
}

type RequiredConfig = Required<Config>;
// { apiUrl: string; timeout: number; }

Kombinieren

type CreateUserInput = Omit<User, "id" | "createdAt">;
type UpdateUserInput = Partial<CreateUserInput>;

// CreateUserInput: { name: string; email: string; password: string; }
// UpdateUserInput: { name?: string; email?: string; password?: string; }

Praktische Patterns

Base Entity Pattern

interface BaseEntity {
    id: number;
    createdAt: Date;
    updatedAt: Date;
}

interface User extends BaseEntity {
    name: string;
    email: string;
}

interface Post extends BaseEntity {
    title: string;
    content: string;
    authorId: number;
}

interface Comment extends BaseEntity {
    text: string;
    postId: number;
    authorId: number;
}

Request/Response Types

interface BaseRequest {
    requestId: string;
    timestamp: Date;
}

interface AuthenticatedRequest extends BaseRequest {
    userId: number;
    token: string;
}

interface CreateUserRequest extends AuthenticatedRequest {
    payload: {
        name: string;
        email: string;
    };
}

// Response Types
interface BaseResponse {
    success: boolean;
    requestId: string;
}

interface DataResponse<T> extends BaseResponse {
    success: true;
    data: T;
}

interface ErrorResponse extends BaseResponse {
    success: false;
    error: {
        code: string;
        message: string;
    };
}

type ApiResponse<T> = DataResponse<T> | ErrorResponse;

State Machine Types

interface BaseState {
    timestamp: Date;
}

interface IdleState extends BaseState {
    status: "idle";
}

interface LoadingState extends BaseState {
    status: "loading";
    progress?: number;
}

interface SuccessState<T> extends BaseState {
    status: "success";
    data: T;
}

interface ErrorState extends BaseState {
    status: "error";
    error: Error;
}

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

function handleState<T>(state: AsyncState<T>): string {
    switch (state.status) {
        case "idle":
            return "Waiting...";
        case "loading":
            return `Loading... ${state.progress ?? 0}%`;
        case "success":
            return `Got data: ${JSON.stringify(state.data)}`;
        case "error":
            return `Error: ${state.error.message}`;
    }
}

Mixin Pattern

type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
        updatedAt = new Date();
    };
}

function Identifiable<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        id = Math.random().toString(36).slice(2);
    };
}

class BaseUser {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

const EnhancedUser = Timestamped(Identifiable(BaseUser));
const user = new EnhancedUser("Max");
console.log(user.id, user.createdAt, user.name);

Zusammenfassung

Interface Extending:

  • extends für Vererbung
  • Mehrfach-Extends möglich
  • Property Override mit kompatiblem Typ

Type Intersection:

  • & für Kombinationen
  • Funktioniert mit Types und Interfaces

Declaration Merging:

  • Nur für Interfaces
  • Nützlich für Library-Erweiterungen

Utility Types:

  • Pick, Omit für Property-Auswahl
  • Partial, Required für Optionalität

Im nächsten Modul lernst du Klassen in TypeScript!

Zurück zum TypeScript Kurs