Skip to content

Kapitel 11: Praxis-Projekte

🎯 Lernziele

In diesem Kapitel lernen Sie:

  • ✅ Datenstrukturen mit Interfaces definieren
  • ✅ Funktionen mit Generics kapseln
  • ✅ Geschäftslogik mit Klassen kapseln
  • ✅ Formulardaten mit Typen einschränken
  • ✅ Eine vollständige TodoList mit TypeScript erstellen

11.1 Datenstrukturen mit Interfaces definieren

📖 Einfache Definition

Verwenden Sie Interfaces, um Datenstrukturen klar zu definieren.

💻 Code-Beispiel

typescript
// Interfaces für eine Blog-Anwendung
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'author' | 'reader';
  createdAt: Date;
}

interface Post {
  id: number;
  title: string;
  content: string;
  author: User;
  tags: string[];
  status: 'draft' | 'published' | 'archived';
  createdAt: Date;
  updatedAt: Date;
}

interface Comment {
  id: number;
  postId: number;
  author: User;
  content: string;
  createdAt: Date;
}

🎨 Praxis-Beispiel

typescript
// API-Response Typen
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
  error?: string;
}

// Verwendung
type GetUserResponse = ApiResponse<User>;
type GetPostsResponse = ApiResponse<Post[]>;
type CreatePostResponse = ApiResponse<Post>;

11.2 Funktionen mit Generics kapseln

📖 Einfache Definition

Verwenden Sie Generics, um wiederverwendbare Tool-Funktionen zu erstellen.

💻 Code-Beispiel

typescript
// Generische API-Funktion
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  return response.json();
}

// Verwendung
const userResponse = await fetchApi<User>('https://api.example.com/users/1');
const postsResponse = await fetchApi<Post[]>('https://api.example.com/posts');

console.log(userResponse.data.name);
console.log(postsResponse.data[0].title);

🎨 Weitere generische Tool-Funktionen

typescript
// 1. Daten filtern
function filterData<T>(data: T[], predicate: (item: T) => boolean): T[] {
  return data.filter(predicate);
}

// 2. Daten sortieren
function sortData<T>(data: T[], compareFn: (a: T, b: T) => number): T[] {
  return [...data].sort(compareFn);
}

// 3. Eindeutige Werte extrahieren
function uniqueBy<T, K extends keyof T>(data: T[], key: K): T[] {
  const seen = new Set<any>();
  return data.filter(item => {
    const value = item[key];
    if (seen.has(value)) return false;
    seen.add(value);
    return true;
  });
}

// Verwendung
const users: User[] = [/* ... */];
const uniqueEmails = uniqueBy(users, 'email');

11.3 Geschäftslogik mit Klassen kapseln

📖 Einfache Definition

Verwenden Sie Klassen, um Geschäftslogik zu kapseln.

💻 Code-Beispiel

typescript
// Service-Klasse für Benutzer-Operationen
class UserService {
  private apiUrl: string;

  constructor(apiUrl: string) {
    this.apiUrl = apiUrl;
  }

  // Alle Benutzer abrufen
  async getAll(): Promise<User[]> {
    const response = await fetch(`${this.apiUrl}/users`);
    return response.json();
  }

  // Benutzer nach ID abrufen
  async getById(id: number): Promise<User | undefined> {
    const response = await fetch(`${this.apiUrl}/users/${id}`);
    return response.json();
  }

  // Benutzer erstellen
  async create(user: Omit<User, 'id' | 'createdAt'>): Promise<User> {
    const response = await fetch(`${this.apiUrl}/users`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(user)
    });
    return response.json();
  }

  // Benutzer aktualisieren
  async update(id: number, updates: Partial<User>): Promise<User> {
    const response = await fetch(`${this.apiUrl}/users/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(updates)
    });
    return response.json();
  }

  // Benutzer löschen
  async delete(id: number): Promise<void> {
    await fetch(`${this.apiUrl}/users/${id}`, {
      method: 'DELETE'
    });
  }
}

// Verwendung
const userService = new UserService('https://api.example.com');

async function main() {
  const users = await userService.getAll();
  console.log(users);
}

11.4 Typen für Formulardaten

📖 Einfache Definition

Verwenden Sie Typen, um Formulardaten zu validieren.

💻 Code-Beispiel

typescript
// Formular-Typen
interface LoginForm {
  email: string;
  password: string;
  rememberMe: boolean;
}

interface RegisterForm {
  name: string;
  email: string;
  password: string;
  confirmPassword: string;
  agreeToTerms: boolean;
}

// Formular-Validierung
function validateLoginForm(form: LoginForm): string[] {
  const errors: string[] = [];

  if (!form.email.includes('@')) {
    errors.push('Ungültige E-Mail-Adresse');
  }

  if (form.password.length < 8) {
    errors.push('Passwort muss mindestens 8 Zeichen lang sein');
  }

  return errors;
}

// Verwendung
const loginForm: LoginForm = {
  email: 'max@example.com',
  password: '12345678',
  rememberMe: true
};

const errors = validateLoginForm(loginForm);
if (errors.length > 0) {
  console.log('Validierungsfehler:', errors);
}

11.5 Praxis: TodoList mit TypeScript

📖 Projekt-Übersicht

Erstellen Sie eine vollständige TodoList-Anwendung mit TypeScript.

Schritt 1: Datenstrukturen definieren

typescript
// src/types/todo.ts

export interface Todo {
  id: number;
  title: string;
  description?: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
  createdAt: Date;
  updatedAt: Date;
}

export type CreateTodoDto = Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>;
export type UpdateTodoDto = Partial<CreateTodoDto>;

export interface TodoFilter {
  completed?: boolean;
  priority?: Todo['priority'];
  search?: string;
}

Schritt 2: Todo-Service erstellen

typescript
// src/services/TodoService.ts

import type { Todo, CreateTodoDto, UpdateTodoDto, TodoFilter } from '../types/todo';

class TodoService {
  private todos: Todo[] = [];
  private nextId: number = 1;

  // Alle Todos abrufen (mit optionalem Filter)
  getAll(filter?: TodoFilter): Todo[] {
    let result = [...this.todos];

    if (filter) {
      if (filter.completed !== undefined) {
        result = result.filter(todo => todo.completed === filter.completed);
      }

      if (filter.priority) {
        result = result.filter(todo => todo.priority === filter.priority);
      }

      if (filter.search) {
        const search = filter.search.toLowerCase();
        result = result.filter(todo => 
          todo.title.toLowerCase().includes(search) ||
          (todo.description && todo.description.toLowerCase().includes(search))
        );
      }
    }

    return result;
  }

  // Todo nach ID abrufen
  getById(id: number): Todo | undefined {
    return this.todos.find(todo => todo.id === id);
  }

  // Todo erstellen
  create(data: CreateTodoDto): Todo {
    const now = new Date();
    const todo: Todo = {
      id: this.nextId++,
      title: data.title,
      description: data.description,
      completed: false,
      priority: data.priority || 'medium',
      createdAt: now,
      updatedAt: now
    };

    this.todos.push(todo);
    return todo;
  }

  // Todo aktualisieren
  update(id: number, updates: UpdateTodoDto): Todo | undefined {
    const todo = this.getById(id);
    if (!todo) return undefined;

    if (updates.title !== undefined) todo.title = updates.title;
    if (updates.description !== undefined) todo.description = updates.description;
    if (updates.completed !== undefined) todo.completed = updates.completed;
    if (updates.priority !== undefined) todo.priority = updates.priority;
    todo.updatedAt = new Date();

    return todo;
  }

  // Todo löschen
  delete(id: number): boolean {
    const index = this.todos.findIndex(todo => todo.id === id);
    if (index === -1) return false;

    this.todos.splice(index, 1);
    return true;
  }

  // Alle Todos löschen
  deleteAll(): void {
    this.todos = [];
    this.nextId = 1;
  }
}

export default TodoService;

Schritt 3: Anwendung erstellen

typescript
// src/app.ts

import TodoService from './services/TodoService';
import type { Todo, CreateTodoDto } from './types/todo';

// Service instanziieren
const todoService = new TodoService();

// Beispiel-Verwendung
function main(): void {
  console.log('=== TodoList Anwendung ===\n');

  // 1. Todos erstellen
  const todo1: CreateTodoDto = {
    title: 'TypeScript lernen',
    description: 'TypeScript Grundlagen verstehen',
    priority: 'high'
  };

  const todo2: CreateTodoDto = {
    title: 'Einkaufen gehen',
    priority: 'medium'
  };

  const created1 = todoService.create(todo1);
  const created2 = todoService.create(todo2);

  console.log('Erstellte Todos:');
  console.log(created1);
  console.log(created2);
  console.log('');

  // 2. Alle Todos abrufen
  const allTodos = todoService.getAll();
  console.log(`Insgesamt ${allTodos.length} Todos\n`);

  // 3. Todo aktualisieren
  todoService.update(created1.id, { completed: true });
  console.log('Todo 1 als erledigt markiert\n');

  // 4. Todos filtern
  const completedTodos = todoService.getAll({ completed: true });
  console.log(`Erledigte Todos: ${completedTodos.length}\n`);

  // 5. Todo löschen
  todoService.delete(created2.id);
  console.log('Todo 2 gelöscht\n');

  // 6. Verbleibende Todos anzeigen
  const remainingTodos = todoService.getAll();
  console.log(`Verbleibende Todos: ${remainingTodos.length}`);
  console.log(remainingTodos);
}

main();

Schritt 4: Kompilieren und ausführen

bash
# TypeScript kompilieren
tsc

# Anwendung ausführen
node dist/app.js

Erwartete Ausgabe:

=== TodoList Anwendung ===

Erstellte Todos:
{ id: 1, title: 'TypeScript lernen', ... }
{ id: 2, title: 'Einkaufen gehen', ... }

Insgesamt 2 Todos

Todo 1 als erledigt markiert

Erledigte Todos: 1

Todo 2 gelöscht

Verbleibende Todos: 1
{ id: 1, title: 'TypeScript lernen', completed: true, ... }

📝 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • Interfaces für Datenstrukturen verwenden
  • Generics für wiederverwendbare Funktionen
  • Klassen für Geschäftslogik
  • Typen für Formulardaten
  • Praktische TodoList mit TypeScript erstellen

🎯 Nächstes Kapitel

Im Kapitel 12 lernen wir häufig gestellte Interviewfragen zu TypeScript!

👉 Weiter zu Kapitel 12: Interviewfragen


❓ Häufig gestellte Fragen

F: Warum Interfaces statt Type Aliases verwenden?

A: Interfaces sind besser für Objekt-Typen, da sie "Declaration Merging" unterstützen und besser lesbar sind.

F: Wann sollte ich Generics verwenden?

A: Wenn Sie Funktionen oder Klassen schreiben, die mit verschiedenen Typen arbeiten sollen.

F: Ist es notwendig, für jedes Formular Typen zu definieren?

A: Ja! Es hilft, Fehler frühzeitig zu erkennen und bietet bessere IDE-Unterstützung.


🎉 Herzlichen Glückwunsch! Sie haben Kapitel 11 abgeschlossen!

Frei für alle Anfänger