Interface vs Type
Verstehe die Unterschiede zwischen Interface und Type Alias in TypeScript und wann du welches verwendest.
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:
-
Du Objekt-Shapes definierst:
interface User { id: number; name: string; } -
Du Klassen-Contracts definierst:
interface Serializable { toJSON(): string; } class Config implements Serializable { toJSON() { return "{}"; } } -
Du Library-Types erweitern musst:
// Erweitere Express Request declare global { namespace Express { interface Request { user?: User; } } } -
Du API-Definitionen schreibst:
interface ApiResponse { status: number; data: unknown; }
Verwende Type wenn:
-
Du Union Types brauchst:
type Status = "loading" | "success" | "error"; -
Du Primitive benennst:
type UserId = number; type Timestamp = number; -
Du Tuples definierst:
type Coordinates = [number, number]; -
Du komplexe Typen komponierst:
type Response<T> = | { status: "success"; data: T } | { status: "error"; error: Error }; -
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
| Feature | Interface | Type |
|---|---|---|
| Objekt-Shapes | ✅ | ✅ |
| Generics | ✅ | ✅ |
| Extends/Intersection | extends | & |
| Declaration Merging | ✅ | ❌ |
| Union Types | ❌ | ✅ |
| Primitive Aliases | ❌ | ✅ |
| Tuples | Umstä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!