navigation Navigation


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.

    StateExample.jsx
    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.

    React State - Konsole Ausgabe vom Component mit ineffizienter Nutzung

    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.

    StateExample.jsx
    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.

    React State - Initiale Werte beim Beispiel für effiziente Nutzung

    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.

    React State - Aktualisierte Werte beim Beispiel für effiziente Nutzung