Appearance
Kapitel 14: Projekt - TodoList
In diesem Kapitel bauen wir eine vollständige TodoList-Anwendung.
14.1 Anforderungsanalyse
✅ Funktionen
- ✅ Aufgaben hinzufügen
- ✅ Aufgaben löschen
- ✅ Aufgaben als erledigt markieren
- ✅ Aufgaben filtern (alle / aktiv / erledigt)
- ✅ LocalStorage-Persistenz
🎨 UI-Struktur
┌─────────────────────────────┐
│ TodoList │
│ ┌─────────────────────┐ │
│ │ [Input] [Hinzufügen]│ │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ ☑ Aufgabe 1 │ │
│ │ ☑ Aufgabe 2 │ │
│ └─────────────────────┘ │
│ [Alle] [Aktiv] [Erledigt]│
└─────────────────────────────┘14.2 Komponenten-Struktur
📂 Dateistruktur
src/
├── App.jsx
├── components/
│ ├── TodoHeader.jsx
│ ├── TodoInput.jsx
│ ├── TodoList.jsx
│ ├── TodoItem.jsx
│ └── TodoFooter.jsx🔗 Komponenten-Hierarchie
App
├── TodoHeader
├── TodoInput
├── TodoList
│ └── TodoItem (mehrere)
└── TodoFooter14.3 State verwalten
📦 Hauptstate in App.jsx
jsx
function App() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all'); // 'all', 'active', 'completed'
// Aufgabe hinzufügen
const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text: text,
completed: false
};
setTodos([...todos, newTodo]);
};
// Aufgabe löschen
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// Aufgabe umschalten (erledigt/aktiv)
const toggleTodo = (id) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// Gefilterte Aufgaben
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true; // 'all'
});
return (
<div className="app">
<TodoHeader />
<TodoInput onAdd={addTodo} />
<TodoList
todos={filteredTodos}
onDelete={deleteTodo}
onToggle={toggleTodo}
/>
<TodoFooter filter={filter} setFilter={setFilter} />
</div>
);
}
export default App;14.4 Komponenten implementieren
1️⃣ TodoHeader.jsx
jsx
function TodoHeader() {
return (
<header className="header">
<h1>TodoList</h1>
<p>Verwalten Sie Ihre Aufgaben effizient</p>
</header>
);
}
export default TodoHeader;2️⃣ TodoInput.jsx
jsx
import { useState } from 'react';
function TodoInput({ onAdd }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
onAdd(text);
setText('');
};
return (
<form className="todo-input" onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Neue Aufgabe..."
/>
<button type="submit">Hinzufügen</button>
</form>
);
}
export default TodoInput;3️⃣ TodoList.jsx & TodoItem.jsx
TodoList.jsx:
jsx
function TodoList({ todos, onDelete, onToggle }) {
if (todos.length === 0) {
return <p className="empty">Keine Aufgaben vorhanden</p>;
}
return (
<ul className="todo-list">
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={onDelete}
onToggle={onToggle}
/>
))}
</ul>
);
}
export default TodoList;TodoItem.jsx:
jsx
function TodoItem({ todo, onDelete, onToggle }) {
return (
<li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Löschen</button>
</li>
);
}
export default TodoItem;4️⃣ TodoFooter.jsx
jsx
function TodoFooter({ filter, setFilter }) {
return (
<div className="todo-footer">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
Alle
</button>
<button
className={filter === 'active' ? 'active' : ''}
onClick={() => setFilter('active')}
>
Aktiv
</button>
<button
className={filter === 'completed' ? 'active' : ''}
onClick={() => setFilter('completed')}
>
Erledigt
</button>
</div>
);
}
export default TodoFooter;14.5 LocalStorage-Persistenz
💾 State in LocalStorage speichern
Benutzerdefinierter Hook: useLocalStorage.js
jsx
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
export default useLocalStorage;🔄 In App.jsx verwenden
jsx
import useLocalStorage from './hooks/useLocalStorage';
function App() {
const [todos, setTodos] = useLocalStorage('todos', []);
// ... restliche Logik bleibt gleich
}14.6 Stile optimieren
🎨 Einfache CSS-Datei (App.css)
css
.app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.todo-input {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.todo-input input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.todo-input button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.todo-input button:hover {
background-color: #0056b3;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-item.completed span {
text-decoration: line-through;
color: #999;
}
.todo-item button {
margin-left: auto;
padding: 5px 10px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.todo-footer {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.todo-footer button {
padding: 8px 16px;
border: 1px solid #ddd;
background-color: white;
cursor: pointer;
border-radius: 4px;
}
.todo-footer button.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.empty {
text-align: center;
color: #999;
margin-top: 20px;
}14.7 Vollständiger Code
Alle Dateien zusammen:
App.jsx(Hauptkomponente mit State)components/TodoHeader.jsxcomponents/TodoInput.jsxcomponents/TodoList.jsxcomponents/TodoItem.jsxcomponents/TodoFooter.jsxhooks/useLocalStorage.jsApp.css(Stile)
📝 Zusammenfassung
In diesem Kapitel haben wir gelernt:
- ✅ Eine vollständige TodoList-Anwendung zu bauen
- ✅ Komponenten-Struktur planen
- ✅ State verwalten (Hinzufügen, Löschen, Umschalten)
- ✅ LocalStorage-Persistenz implementieren
- ✅ Einfache Stile hinzufügen
🎯 Nächste Schritte
Im nächsten Kapitel werden wir:
- Counter-Projekt erstellen (Hooks weiter vertiefen)
- Benutzerdefinierte Hooks üben
- Performance-Optimierung lernen
Bereit für das nächste Projekt? → Kapitel 15: Projekt - Counter
