Lazy Initial State
In React-Anwendungen kann die Initialisierung des Komponentenstatus je nach Komplexität der Logik Auswirkungen auf die Performance haben. Besonders bei rechenintensiven Operationen zur Berechnung des Anfangszustands lohnt es sich, den Initialwert verzögert – also lazy – zu erzeugen. Mit Hilfe eines Initialisierungs-Callbacks lässt sich dieser Vorgang effizient gestalten und unnötige Berechnungen vermeiden.
Inhaltsverzeichnis
Grundproblem
Wenn man useState()
verwendet, wird der angegebene Initialwert bei jedem Rendering der Komponente ausgewertet. Bei einfachen Werten wie Zahlen oder Strings ist das kein Problem.
const [counter, setCounter] = useState(0);
Bei aufwändigen Berechnungen oder teuren Operationen kann es jedoch problematisch werden.
Um dies zu verdeutlichen, betrachten wir ein Beispiel, in dem wir den Zustand einer Komponente mit einem Initialwert über eine Funktion setzen.
In diesem Beispiel verwenden wir die Funktion initState()
, mit der wir einen initialen Wert generieren und zurückgeben.
In der Funktion zum Aktualisieren des Zustandes handleUpdateListItems()
erhöhen wir die Anzahl der Elemente um 1.
Die Logs in der DevTools-Konsole sollen und dabei helfen zu sehen, wie oft die Funktion initState()
aufgerufen wird.
import { useState } from 'react';
const InitialStateInefficient = () => {
const initState = () => {
let newItems = [];
for (let i = 0; i < 100; i++) {
newItems.push(i);
}
console.log('Init state', newItems.length);
return newItems;
};
const [listItems, setListItems] = useState(initState());
const handleUpdateListItems = () => {
setListItems(prevList => {
const newList = [...prevList];
newList.push((newList.length - 1) + 1);
console.log('New state', newList.length);
return newList;
});
};
return (
<>
<button onClick={handleUpdateListItems}>Add Item</button>
</>
);
};
export default InitialStateInefficient;
Wie auf dem folgenden Screenshot zu sehen, wird die Funktion initial aufgerufen, was ganz normal und klar ist. Aber, sie wird auch bei jedem Update des Zustandes erneut ausgeführt. Dabei ist aber die Funktion zur Generierung des initialen Zustandes für uns nicht mehr wichtig. Der initiale Zustand wurde bereits generiert. Hier geht es nur noch um die Aktualisierung des States.
Wenn man sich nun vorstellt, dass diese Operation eine spürbare Zeit in Anspruch nimmt, weil beispielsweise Daten geladen oder komplexere Berechnungen ausgeführt werden, wird es klar, dass das kein optimaler Weg ist, die Funktion, welche für das Setzen des Initialzustandes gedacht war, bei jeder Aktualisierung auszuführen.
Initialer Zustand - Korrekte Vorgehensweise
React bietet für dieses Problem ein spezielles Muster. Man kann anstelle eines direkten Wertes eine Funktion übergeben, die nur beim ersten Rendering ausgeführt wird.
Wir schreiben also das Beispiel von oben entsprechend um und schauen, wie sich nun das Verhalten verändert hat.
import { useState, useEffect } from 'react';
const InitialStateEfficient = () => {
const initState = () => {
let newItems = [];
for (let i = 0; i < 100; i++) {
newItems.push(i);
}
console.log('Init state', newItems.length);
return newItems;
};
const [listItems, setListItems] = useState(() => initState());
const handleUpdateListItems = () => {
setListItems(prevList => {
const newList = [...prevList];
newList.push((newList.length - 1) + 1);
console.log('New state', newList.length);
return newList;
});
};
return (
<>
<button onClick={handleUpdateListItems}>Add Item</button>
</>
);
};
export default InitialStateEfficient;
Wenn wir uns jetzt das Verhalten anschauen, stellen wir fest, dass die Funktion zum Initialisieren zu Beginn weiterhin regulär ausgeführt wird.
Vor dem Klick auf den Button “Add Item” habe ich die Konsole geleert. Nach dem Klick auf den Button sieht man, dass nur der Log mit den aktualisierten Werten in der Konsole erscheint. Die Funktion initState()
wird also bei dieser Verwendung nicht erneut ausgeführt.