TypeScript Advanced Patterns: Generics, Utility Types und Type Guards
TypeScript Advanced Patterns: Generics, Utility Types und Type Guards
TypeScript hat die JavaScript-Entwicklung durch statische Typisierung revolutioniert. Aber um seine Kraft wirklich zu nutzen, müssen Sie fortgeschrittene Muster beherrschen. Lassen Sie uns tief in Generika, Utility-Typen und Typenschutz eintauchen.
Warum Advanced TypeScript wichtig ist
TypeScript zu schreiben ist einfach. Um gutes TypeScript zu schreiben, das wartbar, typsicher und elegant ist, müssen Sie fortgeschrittene Muster verstehen. Diese Muster helfen Ihnen:
- 🎯 Abfangen von Fehlern zur Kompilierungszeit
- Code-Dokumentation 📚 verbessern
- Besseres Refactoring 🔄 ermöglichen
- 💡 Verbessern der automatischen IDE-Vervollständigung
Generics: Einmal schreiben, überall verwenden
Mit Generics können Sie wiederverwendbaren Code schreiben, der mit mehreren Typen funktioniert. ### Basic Generic Function ```typescript function identity<T>(arg: T): T { return arg; }
const num = identity<number>(42); // type: number
const str = identity<string>("hello"); // type: string
const auto = identity(true); // type inferred: boolean
### Generic Constraintstypescript
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }
logLength("hello"); // ✅ Works
logLength([1, 2, 3]); // ✅ Works
logLength({ length: 10 }); // ✅ Works
// logLength(42); // ❌ Error: number doesn't have length
### Generic Interfacestypescript
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(item: Omit<T, "id">): Promise<T>;
update(id: string, item: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
interface User { id: string; name: string; email: string; }
class UserRepository implements Repository<User> { async findById(id: string): Promise<User | null> { // Implementation return null; }
async findAll(): Promise<User[]> { return []; }
async create(item: Omit<User, "id">): Promise<User> { const user: User = { id: "generated", ...item }; return user; }
async update(id: string, item: Partial<User>): Promise<User> { // Implementation return { id, name: "", email: "" }; }
async delete(id: string): Promise<void> {
// Implementation
}
}
## Utility Types: TypeScript's Swiss Army Knife ### Partial<T> - Make All Properties Optionaltypescript
interface User {
id: string;
name: string;
email: string;
age: number;
}
function updateUser(id: string, updates: Partial<User>) { // Can update any subset of properties }
updateUser("1", { name: "John" }); // ✅
updateUser("2", { email: "new@email.com" }); // ✅
updateUser("3", { name: "Jane", age: 30 }); // ✅
### Pick<T, K> - Select Specific Propertiestypescript
type UserPreview = Pick<User, "id" | "name">;
const preview: UserPreview = {
id: "1",
name: "John"
// email and age not required
};
### Omit<T, K> - Exclude Specific Propertiestypescript
type UserCreate = Omit<User, "id">;
const newUser: UserCreate = {
name: "John",
email: "john@example.com",
age: 30
// id is excluded
};
### Record<K, T> - Create Object Type with Specific Keystypescript
type Role = "admin" | "user" | "guest";
type Permissions = Record<Role, string[]>;
const permissions: Permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};
### ReturnType<T> - Extract Function Return Typetypescript
function createUser() {
return {
id: "123",
name: "John",
email: "john@example.com"
};
}
type User = ReturnType<typeof createUser>;
// type User = { id: string; name: string; email: string; }
## Type Guards: Runtime Type Safety ### typeof Type Guardtypescript
function processValue(value: string | number) {
if (typeof value === "string") {
// TypeScript knows value is string here
return value.toUpperCase();
} else {
// TypeScript knows value is number here
return value.toFixed(2);
}
}
### instanceof Type Guardtypescript
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat { meow() { console.log("Meow!"); } }
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // TypeScript knows it's a Dog
} else {
animal.meow(); // TypeScript knows it's a Cat
}
}
### Custom Type Guardstypescript
interface Fish {
swim: () => void;
}
interface Bird { fly: () => void; }
// User-defined type guard function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; }
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // TypeScript knows pet is Fish
} else {
pet.fly(); // TypeScript knows pet is Bird
}
}
### Discriminated Unionstypescript
interface Success {
type: "success";
data: any;
}
interface Error { type: "error"; message: string; }
type Result = Success | Error;
function handleResult(result: Result) {
switch (result.type) {
case "success":
console.log(result.data); // TypeScript knows it's Success
break;
case "error":
console.log(result.message); // TypeScript knows it's Error
break;
}
}
## Advanced Pattern: Conditional Typestypescript
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true type B = IsString<number>; // false
// More practical example type NonNullable<T> = T extends null | undefined ? never : T;
type A = NonNullable<string | null>; // string
type B = NonNullable<number | undefined>; // number
## Advanced Pattern: Mapped Typestypescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Optional<T> = { [P in keyof T]?: T[P]; };
interface User { id: string; name: string; }
type ReadonlyUser = Readonly<User>; // { readonly id: string; readonly name: string; }
type OptionalUser = Optional<User>;
// { id?: string; name?: string; }
## Real-World Example: API Response Handlertypescript
interface ApiResponse<T> {
data?: T;
error?: string;
status: number;
}
// Generic fetch wrapper async function fetchApi<T>(url: string): Promise<ApiResponse<T>> { try { const response = await fetch(url); const data = await response.json();
return {
data,
status: response.status
};
} catch (error) { return { error: error instanceof Error ? error.message : "Unknown error", status: 500 }; } }
// Type-safe usage interface User { id: string; name: string; }
async function getUser(id: string) {
const response = await fetchApi<User>(/api/users/${id});
if (response.error) { console.error(response.error); return null; }
return response.data; // TypeScript knows this is User | undefined }
2. **Leverage Utility Types**: Don't reinvent the wheel
3. **Create Custom Type Guards**: Improve runtime type safety
4. **Use Discriminated Unions**: For complex state management
5. **Keep Types Simple**: Don't over-engineer
## Common Pitfalls to Avoid
❌ **Too Generic**
```typescript
function process<T>(data: T): T {
// Too generic, loses type information
return data;
}
✅ Properly Constrained
Loading code...
Conclusion
Advanced TypeScript patterns transform your code from "typed JavaScript" to truly type-safe, maintainable applications. Master these patterns, and you'll write better code with fewer bugs.
Start small, practice often, and gradually incorporate these patterns into your projects . Your future self (and teammates) will thank you! 🚀