Zum Inhalt springen
TypeScript Anfänger 20 min

Interface vs Type

Verstehe die Unterschiede zwischen Interface und Type Alias in TypeScript und wann du welches verwendest.

Aktualisiert:

Interface vs Type

Eine häufige Frage in TypeScript: Wann verwende ich interface und wann type? Beide können ähnliche Dinge tun, aber es gibt wichtige Unterschiede.

Gemeinsamkeiten

Beide können Objekt-Shapes definieren:

// Interface
interface UserInterface {
    id: number;
    name: string;
    email: string;
}

// Type Alias
type UserType = {
    id: number;
    name: string;
    email: string;
};

// Beide können gleich verwendet werden
const user1: UserInterface = { id: 1, name: "Max", email: "max@example.com" };
const user2: UserType = { id: 2, name: "Anna", email: "anna@example.com" };

Beide unterstützen optionale und readonly Properties:

interface ConfigInterface {
    readonly apiKey: string;
    timeout?: number;
}

type ConfigType = {
    readonly apiKey: string;
    timeout?: number;
};

Beide können generisch sein:

interface ContainerInterface<T> {
    value: T;
}

type ContainerType<T> = {
    value: T;
};

Unterschiede

1. Syntax für Erweiterung

// Interface: extends
interface Animal {
    name: string;
}

interface Dog extends Animal {
    breed: string;
}

// Type: Intersection (&)
type AnimalType = {
    name: string;
};

type DogType = AnimalType & {
    breed: string;
};

2. Declaration Merging

Nur Interfaces können zusammengeführt werden:

// Interface - automatisches Merging
interface User {
    name: string;
}

interface User {
    age: number;
}

// User hat jetzt: { name: string; age: number; }

// Type - Fehler bei Duplikat!
type Person = {
    name: string;
};

type Person = {  // Fehler: Duplicate identifier 'Person'
    age: number;
};

3. Was nur Type kann

Union Types:

type Status = "pending" | "active" | "completed";

// Mit Interface nicht möglich!

Primitive Types:

type ID = number;
type Name = string;

// Mit Interface nicht möglich!

Tuple Types:

type Point = [number, number];
type Response = [boolean, string];

// Mit Interface umständlicher:
interface PointInterface extends Array<number> {
    0: number;
    1: number;
    length: 2;
}

Mapped Types:

type Keys = "name" | "age" | "email";
type Person = {
    [K in Keys]: string;
};

// Mit Interface nicht möglich!

Conditional Types:

type IsString<T> = T extends string ? true : false;

// Mit Interface nicht möglich!

4. Was nur Interface kann

Declaration Merging (wichtig für Library-Erweiterungen):

// Erweitere eine externe Library
declare global {
    interface Window {
        myGlobalVar: string;
    }
}

// Jetzt ist window.myGlobalVar typisiert
window.myGlobalVar = "hello";

implements für Klassen (beide funktionieren, aber Interface ist idiomatischer):

interface Printable {
    print(): void;
}

class Document implements Printable {
    print() {
        console.log("Printing...");
    }
}

5. Error Messages

Interfaces produzieren oft klarere Fehlermeldungen:

interface UserInterface {
    id: number;
    name: string;
}

type UserType = {
    id: number;
    name: string;
};

// Bei Fehlern zeigt TypeScript:
// - "UserInterface" (benannter Typ)
// - "{ id: number; name: string; }" (oft bei Type Aliases)

Performance

Bei sehr großen Projekten können Interfaces marginal schneller sein, weil TypeScript sie anders cached. In der Praxis ist der Unterschied vernachlässigbar.

Empfehlungen

Verwende Interface wenn:

  1. Du Objekt-Shapes definierst:

    interface User {
        id: number;
        name: string;
    }
  2. Du Klassen-Contracts definierst:

    interface Serializable {
        toJSON(): string;
    }
    
    class Config implements Serializable {
        toJSON() { return "{}"; }
    }
  3. Du Library-Types erweitern musst:

    // Erweitere Express Request
    declare global {
        namespace Express {
            interface Request {
                user?: User;
            }
        }
    }
  4. Du API-Definitionen schreibst:

    interface ApiResponse {
        status: number;
        data: unknown;
    }

Verwende Type wenn:

  1. Du Union Types brauchst:

    type Status = "loading" | "success" | "error";
  2. Du Primitive benennst:

    type UserId = number;
    type Timestamp = number;
  3. Du Tuples definierst:

    type Coordinates = [number, number];
  4. Du komplexe Typen komponierst:

    type Response<T> =
        | { status: "success"; data: T }
        | { status: "error"; error: Error };
  5. Du Mapped oder Conditional Types nutzt:

    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    };

Praktisches Beispiel

Ein typisches Projekt könnte beide kombinieren:

// Types für Primitives und Unions
type UserId = number;
type Status = "active" | "inactive" | "pending";

// Interfaces für Objekte
interface User {
    id: UserId;
    name: string;
    status: Status;
}

interface UserRepository {
    findById(id: UserId): User | null;
    findAll(): User[];
    save(user: User): void;
}

// Type für komplexe Kombinationen
type CreateUserInput = Omit<User, "id">;
type UpdateUserInput = Partial<CreateUserInput>;

// Type für Discriminated Unions
type UserAction =
    | { type: "CREATE"; payload: CreateUserInput }
    | { type: "UPDATE"; payload: { id: UserId; data: UpdateUserInput } }
    | { type: "DELETE"; payload: UserId };

Zusammenfassung

FeatureInterfaceType
Objekt-Shapes
Generics
Extends/Intersectionextends&
Declaration Merging
Union Types
Primitive Aliases
TuplesUmständlich
Mapped Types
Conditional Types

Faustregel:

  • Interface für Objekt-Shapes und APIs
  • Type für alles andere (Unions, Primitives, komplexe Kombinationen)

Im nächsten Kapitel lernst du mehr über das Erweitern und Kombinieren von Typen!

Zurück zum TypeScript Kurs