zusätzliche Funktionalität des State Hooks:
mögliches Performance-Problem: der initiale State wird bei jedem Rendering erneut generiert - und wird aber nur beim ersten Rendering benötigt
const [foo, setFoo] = useState(
createObjectWithManyEntries('foo')
);
Lösung: Führe die Initialisierung nur beim ersten Aufruf durch - durch das Übergeben einer Funktion, die nur beim ersten Rendering verwendet wird
const [foo, setFoo] = useState(() =>
createObjectWithManyEntries('foo')
);
grundlegende Version für das Aktualisieren von State:
setCount(count + 1);
alternative Version, die veralteten State vermeidet:
setCount((c) => c + 1);
(siehe nächster Abschnitt)
manchmals haben wir keinen direkten Zugriff auf den aktuellsten State, wenn wir einen neuen State festsetzen
spezielles Szenario: asynchrone Events in Funktionskomponenten (z.B. Netzwerkanfragen)
Szenarien:
Wird in einem Event-Handler ein State-Setter aufgerufen, so ist der neueste State erst nach dem vollständigen Ausführen des Event-Handlers verfügbar
Wird eine asynchrone Aktion aus einer Funktionskomponente ausgelöst, so kann sie während der Ausführung weiter auf alten State verweisen
Beispiel 1:
const [count, setCount] = useState(0);
function incrementTwice() {
setCount(count + 1);
setCount(count + 1);
}
Der Code setzt count
zweimal hintereinander auf 1
Beispiel 2:
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
function incrementWithDelay() {
setTimeout(() => setCount(count + 1), 3000);
}
Szenario für Beispiel 2:
count
startet bei 0incrementWithDelay
wird aufgerufenincrement
aufgerufenincrement
erneut aufgerufenincrementWithDelay
den WertWerte von count
: 0 → 1 → 2 → 1
Erklärung von Beispiel 2:
Unterschied zwischen Funktionskomponenten und Klassenkomponenten wenn sich Props / State ändern:
this.props
und this.state
durch neue Objekte ersetztAchtung: in Funktionskomponenten können alte / obsolete Daten in alten Closures weiterbestehen
Beispiel: fehlerhafter Code, der in einer Closure weiter auf einen veralteten State-Eintrag verweist
function Counter() {
const [count, setCount] = useState(0);
function startCounting() {
setInterval(() => {
// This innermost function will only be created once.
// The variable "count" will always refer to the
// state from the initial rendering (0)
setCount(count + 1);
}, 1000);
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => startCounting()}>start</button>
</div>
);
}
ESLint-Regel, die helfen kann, veraltete Daten in einem Effect-Hook zu erkennen:
react-hooks/exhaustive-deps
(in VS Code, installiere hierfür das ESLint-Plugin)
mögliche Lösungen (siehe auch: Hinweise in den Linter-Meldungen):
mögliche Lösung: "State-Transformations-Funktion"
ersetze dies:
setCount(count + 1);
to this:
setCount((c) => c + 1);
Die innere Funktion bekommt immer den aktuellsten Wert übergeben
mögliche Lösung: zusätzliches Speichern von Daten in einer Ref (wird später genauer erklärt)
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
function startCounting() {
setInterval(() => {
countRef.current++;
setCount(countRef.current);
}, 1000);
}
// ...
}