Umfassende Projektpraxis mit TypeScript

Dieses Tutorial verwendet ein vollständiges Projektbeispiel, um verschiedene Funktionen von TypeScript umfassend anzuwenden.

Von der Projekteinrichtung bis zur tatsächlichen Entwicklung zeigt es vollständig die Anwendung von TypeScript in realen Projekten.


SVG-Diagramm: Projektarchitektur Background Title Project Practice: Task Management System Frontend Frontend (React + TS) Vite + React 18 Components + Hooks Type-safe UI Arrow Backend Backend (Node.js) Express + TS RESTful API Type Definitions Arrow Data Data Storage In-Memory (Demo) Lower Section: Features Project Features Feature 1 Task CRUD Operations Feature 2 Type-safe API Feature 3 State Management Arrow Marker

Warum brauchen wir umfassende Projektpraxis?

Nachdem Sie die TypeScript-Syntax gelernt haben, müssen Sie Ihr Wissen durch reale Projekte festigen.

Dieses Tutorial demonstriert ein vollständiges Aufgabenverwaltungssystem, das Frontend, Backend und Typdefinitionen abdeckt.

Durch dieses Projekt können Sie die Best Practices von TypeScript in der realen Entwicklung beherrschen.

Projektziele:Erstellen Sie ein Aufgabenverwaltungssystem mit Funktionen zum Erstellen, Abfragen, Aktualisieren und Löschen von Aufgaben.


Projektstruktur

Organisieren Sie die Projektstruktur im Monorepo-Stil.

Verzeichnisstruktur

task-manager/
├── src/
│ ├── types/# Typdefinitionen
│ │ ├── task.ts# Task-Typen
│ │ ├── api.ts# API-Typen
│ │ └── index.ts# Typ-Exporte
│   │
│ ├── services/# Service-Schicht
│ │ ├── taskService.ts# Task-Service
│ │ └── index.ts
│   │
│ ├── components/# React-Komponenten
│ │ ├── TaskList.tsx# Task-Liste
│ │ ├── TaskItem.tsx# Task-Element
│ │ ├── TaskForm.tsx# Task-Formular
│ │ └── index.ts
│   │
│ ├── hooks/# Benutzerdefinierte Hooks
│ │ ├── useTasks.ts# Task-Zustandsverwaltung
│ │ └── index.ts
│   │
│ ├── App.tsx# Haupt-App
│ ├── App.css# Stile
│ └── main.tsx# Einstiegsdatei
│
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

Verzeichnisaufteilung:Verzeichnisse werden nach Funktion aufgeteilt, wobei Typdefinitionen, Service-Schicht und Komponenten getrennt werden.


Typdefinitionen

Definiere zuerst die Kerntypen des Projekts.

src/types/task.ts

// Task-Status-Enum
export type TaskStatus = "pending" | "in-progress" | "completed";

// Task-Prioritäts-Enum
export type TaskPriority = "low" | "medium" | "high";

// Task-Interface-Definition
export interface Task {
    id: string;               // Task-ID
    title: string;            // Task-Titel
    description?: string;      // Task-Beschreibung (optional)
    status: TaskStatus;       // Task-Status
    priority: TaskPriority;   // Task-Priorität
    createdAt: string;        // Erstellungszeit
    updatedAt: string;         // Aktualisierungszeit
    dueDate?: string;         // Fälligkeitsdatum (optional)
    tags?: string[];          // Schlagwörter (optional)
}

// Eingabetyp zum Erstellen einer Aufgabe
export interface CreateTaskInput {
    title: string;
    description?: string;
    priority: TaskPriority;
    dueDate?: string;
    tags?: string[];
}

// Eingabetyp zum Aktualisieren einer Aufgabe
export interface UpdateTaskInput {
    title?: string;
    description?: string;
    status?: TaskStatus;
    priority?: TaskPriority;
    dueDate?: string;
    tags?: string[];
}

// Task-Filteroptionen
export interface TaskFilter {
    status?: TaskStatus;
    priority?: TaskPriority;
    search?: string;
}

Typenschichtung:Definieren Sie Eingabetypen, Ausgabetypen und Filtertypen getrennt, um die Wartung zu erleichtern.


API-Typdefinitionen

Definieren Sie API-bezogene Typen.

< h2 class="example">src/types/api.ts
// Generischer API-Antworttyp
export interface ApiResponse<T> {
    success: boolean;
    data?: T;
    error?: string;
    message?: string;
}

// Paginierungs-Metadaten
export interface PaginationMeta {
    total: number;
    page: number;
    pageSize: number;
    totalPages: number;
}

// Paginierungs-Antworttyp
export interface PaginatedResponse<T> {
    items: T[];
    meta: PaginationMeta;
}

// Anfragefehlertyp
export interface ApiError {
    Code: string;
    message: string;
    details?: Record<string, string>;
}

// HTTP-Methodentyp
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";

// Task-bezogene API-Endpunkte
export interface TaskEndpoints {
    getAll: "/api/tasks";
    getById: "/api/tasks/:id";
    create: "/api/tasks";
    update: "/api/tasks/:id";
    delete: "/api/tasks/:id";
}

API-Typen:Einheitliches Antwortformat und Fehlerbehandlungstypen erleichtern die Frontend-Backend-Integration.


Task-Service-Schicht

Implementiere die Geschäftslogik für die Aufgabenverwaltung.

src/services/taskService.ts

// Typdefinitionen importieren
import {
    Aufgabe,
    CreateTaskInput,
    UpdateTaskInput,
    TaskFilter,
    TaskStatus,
    TaskPriority
} from "../types/task";

// Eindeutige ID generieren
function generateId(): string {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
}

// Mock-Datenbank (In-Memory-Speicher)
let tasks: Task[] = [
    {
        id: "1",
        title: "TypeScript lernen",
        description: "TypeScript-Grundlagen und fortgeschrittene Features meistern",
        status: "abgeschlossen",
        priority: "hoch",
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        tags: ["Lernen", "TypeScript"]
    },
    {
        id: "2",
        title: "Ein Aufgabenverwaltungssystem entwickeln",
        description: "Mit React + TypeScript entwickeln",
        status: "in Bearbeitung",
        priority: "hoch",
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        tags: ["Project", "Practice"]
    }
];

// Task-Service-Klasse
class TaskService {
    // Alle Aufgaben abrufen
    getAll(filter?: TaskFilter): Task[] {
        let result = [...tasks];

        if (filter) {
            if (filter.status) {
                result = result.filter(t => t.status === filter.status);
            }
            if (filter.priority) {
                result = result.filter(t => t.priority === filter.priority);
            }
            if (filter.search) {
                const search = filter.search.toLowerCase();
                result = result.filter(t =>
                    t.title.toLowerCase().includes(search) ||
                    t.description?.toLowerCase().includes(search)
                );
            }
        }

        return result;
    }

    // Aufgabe nach ID abrufen
    getById(id: string): Task | undefined {
        return tasks.find(t => t.id === id);
    }

    // Aufgabe erstellen
    create(input: CreateTaskInput): Task {
        const now = new Date().toISOString();
        const task: Task = {
            id: generateId(),
            title: input.title,
            description: input.description,
            status: "pending",
            priority: input.priority,
            createdAt: now,
            updatedAt: now,
            dueDate: input.dueDate,
            tags: input.tags
        };

        tasks.push(task);
        return task;
    }

    // Aufgabe aktualisieren
    update(id: string, input: UpdateTaskInput): Task | null {
        const index = tasks.findIndex(t => t.id === id);
        if (index === -1) return null;

        const task = tasks[index];
        const updated: Task = {
            ...task,
            ...input,
            updatedAt: new Date().toISOString()
        };

        tasks[index] = updated;
        return updated;
    }

    // Aufgabe löschen
    delete(id: string): boolean {
        const index = tasks.findIndex(t => t.id === id);
        if (index === -1) return false;

        tasks.splice(index, 1);
        return true;
    }

    // Aufgabenstatus aktualisieren
    updateStatus(id: string, status: TaskStatus): Task | null {
        return this.update(id, { status });
    }
}

// Serviceinstanz exportieren
export const taskService = new TaskService();

Service-Schicht:Die Geschäftslogik ist in der Service-Schicht zentralisiert, um einfaches Testen und Warten zu ermöglichen.


Benutzerdefinierter Hook

Hook zum Verwalten des Aufgabenstatus verwenden.

< h2 class="example">src/hooks/useTasks.ts
// React-Hooks und Typen importieren
import { useState, useEffect, useCallback } from "react";
import {
    Aufgabe,
    CreateTaskInput,
    UpdateTaskInput,
    TaskFilter,
    TaskStatus,
    TaskPriority
} from "../types/task";
import { taskService } from "../services/taskService";

// Vom Hook zurückgegebener Zustandstyp
interface UseTasksReturn {
    tasks: Task[];
    loading: boolean;
    error: string | null;
    filter: TaskFilter;
    // Operationsmethoden
    createTask: (input: CreateTaskInput) => Promise<void>;
    updateTask: (id: string, input: UpdateTaskInput) => Promise<void>;
    deleteTask: (id: string) => Promise<void>;
    updateStatus: (id: string, status: TaskStatus) => Promise<void>;
    setFilter: (filter: TaskFilter) => void;
    refresh: () => void;
}

// Standardfilter initialisieren
const defaultFilter: TaskFilter = {};

export function useTasks(): UseTasksReturn {
    // Status der Aufgabenliste
    const [tasks, setTasks] = useState<Task[]>([]);
    // Ladezustand
    const [loading, setLoading] = useState(true);
    // Fehlerzustand
    const [error, setError] = useState<string | null>(null);
    // Filterbedingungen
    const [filter, setFilter] = useState<TaskFilter>(defaultFilter);

    // Aufgabenliste laden
    const loadTasks = useCallback(() => {
        setLoading(true);
        setError(null);

        try {
            const data = taskService.getAll(filter);
            setTasks(data);
        } catch (err) {
            setError(err instanceof Error ? err.message : "Laden fehlgeschlagen");
        } finally {
            setLoading(false);
        }
    }, [filter]);

    // Beim initialen Laden und bei Filteränderungen neu laden
    useEffect(() => {
        loadTasks();
    }, [loadTasks]);

    // Aufgabe erstellen
    const createTask = useCallback(async (input: CreateTaskInput) => {
        try {
            taskService.create(input);
            loadTasks();
        } catch (err) {
            setError(err instanceof Error ? err.message : "Erstellen fehlgeschlagen");
        }
    }, [loadTasks]);

    // Aufgabe aktualisieren
    const updateTask = useCallback(async (id: string, input: UpdateTaskInput) => {
        try {
            taskService.update(id, input);
            loadTasks();
        } catch (err) {
            setError(err instanceof Error ? err.message : "Aktualisieren fehlgeschlagen");
        }
    }, [loadTasks]);

    // Aufgabe löschen
    const deleteTask = useCallback(async (id: string) => {
        try {
            taskService.delete(id);
            loadTasks();
        } catch (err) {
            setError(err instanceof Error ? err.message : "Löschen fehlgeschlagen");
        }
    }, [loadTasks]);

    // Aufgabenstatus aktualisieren
    const updateStatus = useCallback(async (id: string, status: TaskStatus) => {
        try {
            taskService.updateStatus(id, status);
            loadTasks();
        } catch (err) {
            setError(err instanceof Error ? err.message : "Statusaktualisierung fehlgeschlagen");
        }
    }, [loadTasks]);

    // Aufgabenliste aktualisieren
    const refresh = useCallback(() => {
        loadTasks();
    }, [loadTasks]);

    return {
        tasks,
        loading,
        error,
        filter,
        createTask,
        updateTask,
        deleteTask,
        updateStatus,
        setFilter,
        refresh
    };
}

Benutzerdefinierter Hook:Kapseln Sie die Zustandsverwaltung und die Geschäftslogik in einem Hook, wodurch Komponenten prägnanter werden.


React-Komponenten

Verwende den Hook, um eine Aufgabenlisten-Komponente zu erstellen.

src/components/TaskList.tsx

// React und den benutzerdefinierten Hook importieren
import React from "react";
import { useTasks } from "../hooks/useTasks";
import { Task, TaskStatus, TaskPriority } from "../types/task";

// Props der Aufgaben-Element-Komponente
interface TaskItemProps {
    task: Task;
    onStatusChange: (id: string, status: TaskStatus) => void;
    onDelete: (id: string) => void;
}

// Aufgaben-Element-Komponente
const TaskItem: React.FC<TaskItemProps> = ({
    task,
    onStatusChange,
    onDelete
}) => {
    // Status-Stil-Zuordnung
    const statusStyles: Record<TaskStatus, string> = {
        "pending": "status-pending",
        "in-progress": "status-progress",
        "completed": "status-completed"
    };

    // Prioritätsstil-Zuordnung
    const priorityLabels: Record<TaskPriority, string> = {
        "low": "Niedrig",
        "medium": "Mittel",
        "high": "Hoch"
    };

    return (
        <div className={`task-item ${statusStyles[task.status]}`}>
            <div className="task-content">
                <h3 className="task-title">{task.title}</h3>
                {task.description && (
                    <p className="task-description">{task.description}</p>
                )}
                <div className="task-meta">
                    <span className={`priority priority-${task.priority}`}>
                        {priorityLabels[task.priority]}
                    </span>
                    <span className="task-date">
                        {new Date(task.createdAt).toLocaleDateString()}
                    </span>
                </div>
            </div>
            <div className="task-actions">
                <select
                    value={task.status}
                    onChange={(e) => onStatusChange(task.id, e.target.value as TaskStatus)}
                    className="status-select"
                >
                    <option value="pending">Ausstehend</option>
                    <option value="in-progress">In Bearbeitung</option>
                    <option value="completed">Abgeschlossen</option>
                </select>
                <button
                    onClick={() => onDelete(task.id)}
                    className="delete-btn"
                >
Delete
                </button>
            </div>
        </div>
    );
};

// Aufgabenlisten-Komponente
export const TaskList: React.FC = () => {
    // Benutzerdefinierten Hook verwenden, um Aufgabenstatus abzurufen
    const {
        tasks,
        loading,
        error,
        updateStatus,
        deleteTask
    } = useTasks();

    // Ladezustand rendern
    if (loading) {
        return <div className="loading">Wird geladen...</div>;
    }

    // Fehlerzustand rendern
    if (error) {
        return <div className="error">Fehler: {error}</div>;
    }

    // Leerzustand rendern
    if (tasks.length === 0) {
        return (
            <div className="empty">
                <p>Noch keine Aufgaben</p>
                <p>Klicke auf den Button oben, um eine neue Aufgabe zu erstellen</p>
            </div>
        );
    }

    // Aufgabenliste rendern
    return (
        <div className="task-list">
            {tasks.map(task => (
                <TaskItem
                    key={task.id}
                    task={task}
                    onStatusChange={updateStatus}
                    onDelete={deleteTask}
                />
            ))}
        </div>
    );
};

export default TaskList;

Komponentenaufteilung:TaskItem in eine unabhängige Komponente aufteilen, wodurch der Code klarer wird.


Haupt-App-Komponente

Alle Komponenten integrieren, um eine vollständige Anwendung zu bilden.

src/App.tsx

// React und Typen importieren
import React, { useState } from "react";
import { TaskList } from "./components/TaskList";
import { useTasks } from "./hooks/useTasks";
import { CreateTaskInput, TaskPriority } from "./types/task";
import "./App.css";

// Props der Task-Formular-Komponente
interface TaskFormProps {
    onSubmit: (input: CreateTaskInput) => void;
}

// Task-Formular-Komponente
const TaskForm: React.FC<TaskFormProps> = ({ onSubmit }) => {
    // Formularzustand
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");
    const [priority, setPriority] = useState<TaskPriority>("medium");

    // Absenden behandeln
    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();

        if (!title.trim()) {
            alert("Bitte geben Sie einen Aufgabentitel ein");
            return;
        }

        onSubmit({
            title: title.trim(),
            description: description.trim() || undefined,
            priority
        });

        // Formular zurücksetzen
        setTitle("");
        setDescription("");
        setPriority("medium");
    };

    return (
        <form onSubmit={handleSubmit} className="task-form">
            <input
                type="text"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                placeholder="Aufgabentitel eingeben"
                className="form-input"
            />
            <input
                type="text"
                value={description}
                onChange={(e) => setDescription(e.target.value)}
                placeholder="Aufgabenbeschreibung eingeben (optional)"
                className="form-input"
            />
            <select
                value={priority}
                onChange={(e) => setPriority(e.target.value as TaskPriority)}
                className="form-select"
            >
                <option value="low">Niedrige Priorität</option>
                <option value="medium">Mittlere Priorität</option>
                <option value="high">Hohe Priorität</option>
            </select>
            <button type="submit" className="submit-btn">
Aufgabe hinzufügen
            </button>
        </Form>
    );
};

// Haupt-App-Komponente
const App: React.FC = () => {
    // Benutzerdefinierten Hook verwenden
    const { createTask, error } = useTasks();

    // Fehlermeldung rendern
    const renderError = () => {
        if (!error) return null;
        return <div className="app-error">{error}</div>;
    };

    return (
        <div className="app">
            <header className="app-header">
                <h1>TypeScript-Aufgabenverwaltungssystem</h1>
                <p>Umfassendes Übungsprojekt</p>
            </header>

            {renderError()}

            <main className="app-main">
                <TaskForm onSubmit={createTask} />
                <TaskList />
            </main>

            <footer className="app-footer">
                <p>Bereitgestellt von TypeScript + React</p>
            </footer>
        </div>
    );
};

export default App;

Komponentenkomposition:Komplexe UI durch Zusammensetzen einfacher Komponenten erstellen.


Projektkonfiguration

Die TypeScript- und Build-Konfiguration des Projekts.

tsconfig.json

{
    "compilerOptions": {
        "target": "ES2020",
        "useDefineForClassFields": true,
        "lib": ["ES2020", "DOM", "DOM.Iterable"],
        "module": "ESNext",
        "skipLibCheck": true,

        "moduleResolution": "bundler",
        "allowImportingTsExtensions": true,
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx",

        "strict": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true
    },
    "include": ["src"],
    "references": [{ "path": "./tsconfig.node.json" }]
}

Strict-Modus:Aktiviere strict für die umfassendste Typprüfung.


Hinweise

  • Typen zuerst:Definiere zuerst Typen, dann schreibe Implementierungscode.
  • Interface vs. Typ:Verwende Interfaces für Objekttypen, Typaliase für Union-Typen.
  • Schichtenarchitektur:Organisiere Typen, Services und Komponenten in Schichten.
  • Hook-Kapselung:Geschäftslogik in Hooks kapseln.

Best Practices:Typdefinitionen sind die Grundlage eines TypeScript-Projekts; entwerfen Sie sie sorgfältig.


Zusammenfassung

In diesem umfassenden Projekt haben wir die Kernkonzepte von TypeScript geübt.

  • Typdefinitionen:Interfaces wie Task, CreateTaskInput, TaskFilter.
  • Service-Schicht:TaskService kapselt Geschäftslogik.
  • Benutzerdefinierte Hooks:useTasks verwaltet den Status.
  • React-Komponenten:Typsichere Komponentenentwicklung.
  • Projektkonfiguration:Strikte TypeScript-Konfiguration.

Empfehlungen:Nehmen Sie an mehr echten Projekten teil, um Ihr Verständnis von TypeScript durch Praxis zu vertiefen.

Weitere Erweiterungen