Appearance
Kapitel 15: Projekt - Counter
In diesem Kapitel bauen wir eine Counter-Anwendung, um React Hooks zu üben.
15.1 Anforderungsanalyse
✅ Funktionen
- ✅ Zähler erhöhen
- ✅ Zähler verringern
- ✅ Zähler zurücksetzen
- ✅ Schrittweite ändern
- ✅ Zähler speichern (LocalStorage)
🎨 UI-Struktur
┌─────────────────────────────┐
│ Counter │
│ ┌─────────────────────┐ │
│ │ │ │
│ │ Zähler: 10 │ │
│ │ │ │
│ └─────────────────────┘ │
│ [+] [-] [Reset] │
│ Schrittweite: [ 1 ] │
└─────────────────────────────┘15.2 useReducer implementieren
📦 useReducer Grundlagen
useReducer ist eine Alternative zu useState für komplexe Zustandslogik.
jsx
import { useReducer } from 'react';
// Reducer-Funktion
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'reset':
return { ...state, count: 0 };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
function Counter() {
const initialState = { count: 0, step: 1 };
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div className="counter">
<h2>Zähler: {state.count}</h2>
<div className="buttons">
<button onClick={() => dispatch({ type: 'increment' })}>
+
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
-
</button>
<button onClick={() => dispatch({ type: 'reset' })}>
Reset
</button>
</div>
<div className="step">
<label>Schrittweite:</label>
<input
type="number"
value={state.step}
onChange={(e) => dispatch({
type: 'setStep',
payload: Number(e.target.value)
})}
/>
</div>
</div>
);
}
export default Counter;15.3 Custom Hook: useCounter
🔄 Benutzerdefinierter Hook
hooks/useCounter.js:
jsx
import { useReducer } from 'react';
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'reset':
return { ...state, count: 0 };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
function useCounter(initialCount = 0, initialStep = 1) {
const initialState = { count: initialCount, step: initialStep };
const [state, dispatch] = useReducer(counterReducer, initialState);
const increment = () => dispatch({ type: 'increment' });
const decrement = () => dispatch({ type: 'decrement' });
const reset = () => dispatch({ type: 'reset' });
const setStep = (step) => dispatch({ type: 'setStep', payload: step });
return {
count: state.count,
step: state.step,
increment,
decrement,
reset,
setStep
};
}
export default useCounter;📦 Verwendung
Counter.jsx:
jsx
import useCounter from './hooks/useCounter';
function Counter() {
const { count, step, increment, decrement, reset, setStep } = useCounter();
return (
<div className="counter">
<h2>Zähler: {count}</h2>
<div className="buttons">
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
<div className="step">
<label>Schrittweite:</label>
<input
type="number"
value={step}
onChange={(e) => setStep(Number(e.target.value))}
/>
</div>
</div>
);
}
export default Counter;Vorteile:
- Logik ist wiederverwendbar
- Komponente ist einfacher (weniger Code)
- Testing ist einfacher
15.4 Formular-Bindung (Schrittweite)
📝 Schrittweite ändern
jsx
function Counter() {
const { count, step, increment, decrement, reset, setStep } = useCounter();
const handleStepChange = (e) => {
const value = Number(e.target.value);
if (value > 0) {
setStep(value);
}
};
return (
<div className="counter">
<h2>Zähler: {count}</h2>
<div className="buttons">
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
<div className="step">
<label>Schrittweite:</label>
<input
type="number"
min="1"
value={step}
onChange={handleStepChange}
/>
</div>
</div>
);
}Wichtig:
value={step}: Aktueller Wert aus StateonChange={handleStepChange}: Aktualisiert State bei Änderung
15.5 Stile hinzufügen
🎨 CSS-Datei (Counter.css)
css
.counter {
max-width: 400px;
margin: 0 auto;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.counter h2 {
font-size: 2rem;
margin-bottom: 20px;
color: #333;
}
.buttons {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
.buttons button {
padding: 10px 20px;
font-size: 1.2rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.buttons button:hover {
background-color: #0056b3;
}
.step {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.step label {
font-weight: bold;
}
.step input {
width: 80px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
}15.6 Vollständiger Code
App.jsx:
jsx
import Counter from './components/Counter';
import './App.css';
function App() {
return (
<div className="app">
<h1>Counter-Projekt</h1>
<Counter />
</div>
);
}
export default App;Counter.jsx:
jsx
import { useReducer } from 'react';
import './Counter.css';
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'reset':
return { ...state, count: 0 };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0, step: 1 });
return (
<div className="counter">
<h2>Zähler: {state.count}</h2>
<div className="buttons">
<button onClick={() => dispatch({ type: 'increment' })}>
+
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
-
</button>
<button onClick={() => dispatch({ type: 'reset' })}>
Reset
</button>
</div>
<div className="step">
<label>Schrittweite:</label>
<input
type="number"
value={state.step}
onChange={(e) => dispatch({
type: 'setStep',
payload: Number(e.target.value)
})}
/>
</div>
</div>
);
}
export default Counter;📝 Zusammenfassung
In diesem Kapitel haben wir gelernt:
- ✅ Eine Counter-Anwendung zu bauen
- ✅
useReducerfür komplexe Zustandslogik verwenden - ✅ Benutzerdefinierte Hooks (
useCounter) erstellen - ✅ Formular-Bindung (Schrittweite ändern)
- ✅ Stile hinzufügen
🎯 Nächste Schritte
Im nächsten Kapitel werden wir:
- Unternehmensebene Projekt bauen (Blog-System)
- React Router verwenden
- Axios für API-Aufrufe verwenden
Bereit für das nächste Projekt? → Kapitel 16: Projekt - Blog-System
