useMemo()
Der useMemo Hook ist eine leistungsstarke Optimierungsfunktion in React, die durch Memoization berechneter Werte die Anwendungsperformance verbessert. Eingeführt mit React 16.8 ermöglicht dieser Hook das Zwischenspeichern rechenintensiver Funktionsergebnisse, die nur neu berechnet werden, wenn bestimmte Abhängigkeiten sich ändern. In komplexen Komponenten mit kostspieligen Berechnungen verhindert useMemo unnötige Neuberechnungen bei Rendervorgängen und trägt so zu effizienteren, reaktionsschnelleren Benutzeroberflächen bei.
Inhaltsverzeichnis
Einführung
useMemo()
ist ein React Hook, der dazu dient, eine teure Berechnung (oder allgemein eine Funktion mit Rückgabewert) zu ‘memorieren’ - also das Ergebnis dieser Berechnung zwischenzuspeichern, damit React sie nicht bei jedem Render der Komponente erneut ausführen muss.
Mit anderen Worten: useMemo()
optimiert die Performance, indem es verhindert, dass aufwändige Berechnungen unnötig bei jedem Rendern wiederholt werden, sofern sich die Eingaben (Abhängigkeiten) nicht geändert haben.
Syntax
Die Syntax bzw. der grundlegende Aufbau der Verwendung von useMemo()
sieht wie folgt aus.
const memoizedValue = useMemo(() => {
// Teure Berechnung oder Funktion
return computeExpensiveValue(a, b);
}, [a, b]);
Parameter
Erster Parameter: Eine Funktion, die den Wert berechnet und zurückgibt.
Zweiter Parameter: Ein Array von Abhängigkeiten, das React überwacht. Ändert sich eine Abhängigkeit, wird die Funktion erneut ausgeführt.
Funktionsweise
- React merkt sich bei jedem Rendern den zurückgegebenen Wert von
useMemo()
. - Wenn sich keine der Abhängigkeiten geändert haben, liefert React beim nächsten Rendern das gespeicherte Ergebnis zurück, ohne die Berechnungsfunktion erneut auszuführen.
- Erst wenn sich eine der Abhängigkeiten ändert, wird die Berechnungsfunktion wieder ausgeführt.
Beispiel 1
In diesem Beispiel schauen wir uns einen Fall ohne useMemo()
an und werden im Anschluß ein Optimierung mit useMemo()
durchführen.
import { useState } from 'react';
function UseMemoExampleOne() {
const [counter, setCounter] = useState(0);
const [name, setName] = useState('');
const calculate = () => {
console.log('Berechne ...');
let result = 0;
for (let i = 0; i < 100000; i++) {
result += 1;
}
return result;
};
const calculationResult = calculate();
return (
<div>
<p>Ergebnis: {calculationResult}</p>
<button onClick={() => setCounter(counter + 1)}>
Zähler erhöhen ({counter})
</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
}
export default UseMemoExampleOne;
In diesem Beispiel würde die Funktion calculate()
auch dann laufen, wenn man etwas in das Eingabefeld eingeben würde. Dabei ist es nicht an dieser Stelle gar nicht notwendig, diese Berechnung auszuführen. Lediglich die Beim Klick auf ‘Zähler erhöhen’ sollte die Berechnung neu ausgeführt werden.
Damit wir nur dann eine Berechnung, wenn sich nur bestimmte Werte ändern, können wir useMemo()
mit Abhängigkeiten verwenden.
import { useState } from 'react';
function UseMemoExampleOne() {
const [counter, setCounter] = useState(0);
const [name, setName] = useState('');
const result = useMemo(() => {
console.log('Berechne ...');
let result = 0;
for (let i = 0; i < 100000; i++) {
result += 1;
}
return result;
}, [counter]);
return (
<div>
<p>Ergebnis: {result}</p>
<button onClick={() => setCounter(counter + 1)}>
Zähler erhöhen ({counter})
</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
}
export default UseMemoExampleOne;
In diesem Fall haben wir useMemo()
im Einsatz. Zusätzlich haben wir als zweiten Parameter die Variable counter
übergeben. Das bedeutet, dass nur bei Vorliegen eines neuen Werten von counter
die Funktion erneut ausgeführt wird.
Beispiel 2
In diesem Beispiel werden wir in einer Liste von Zahlen die größte Zahl finden. Die Ermittlung der Zahl wird jedes Mal ausgeführt, nachdem eine neue, zufällige Zahl generiert wurde.
Wir bauen das Beispiel schrittweise aus, damit der Weg verständlicher ist.
Wir definieren erstmal unsere HTML-Elemente, damit man eine Vorstellung hat, was später gerendert wird.
function UseMemoExampleTwo() {
return (
<div>
<p>Die größte Zahl ist: </p>
<hr />
<button>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: </p>
<button>
Hilfs-Zähler erhöhen
</button>
</div>
);
}
export default UseMemoExampleTwo;
So, das wird die Struktur des Components sein. Wir haben hier einen Button zum Erhöhen des Hilfszählers hinzugefügt. Die einzige Aufgabe von diesem Button wird sein, einen Hilfs-Wert zu ändern, um das Re-Rending des Components (dank useState()
) auszulösen.
Im nächsten Schritt definieren wir eine Funktion, die wir für das Finden der größten Zahl verwenden werden.
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
};
Diese Funktion beinhaltet eine for
Schleife, die eigentlich nichts tut. Ich habe sie hier platziert, als eine Art Simulation, dass dies eine komplexere Berechnung sein könnte.
Sonst gibt die Funktion nur die größte Zahl mithilfe von Math.max
zurück.
Übernehmen wir diese Funktion in den bestehenden Code.
function UseMemoExampleTwo() {
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
};
return (
<div>
<p>Die größte Zahl ist: </p>
<hr />
<button>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: </p>
<button>
Hilfs-Zähler erhöhen
</button>
</div>
);
}
export default UseMemoExampleTwo;
Im nächsten Schritt definieren wir ein paar States, um Daten festzuhalten. Zum Einen brauchen wir einen Zustand für die Zahlen-Liste und zum anderen, wie bereits erwähnt, irgendein Zustand, welchen wir ändern, um das Re-Rendering des Components zu erzwingen.
const [nums, setNums] = useState([1, 5, 2, 8, 3]);
const [helperCounter, setHelperCounter] = useState(0);
Bei der Zahlen-Liste starten wir den Zahlen 1, 5, 2, 8, 3. Beim Helfer-Zähler bzw. beim Helfer-Zustand ist der Wert völlig egal. Das Wichtigste bei diesem Zustand ist, dass wir irgendwie den Wert ändern.
Übernehmen wir nun auch das in unseren Code.
import { useState } from 'react';
function UseMemoExampleTwo() {
const [nums, setNums] = useState([1, 5, 2, 8, 3]);
const [helperCounter, setHelperCounter] = useState(0);
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
};
return (
<div>
<p>Die größte Zahl ist: </p>
<hr />
<button>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: </p>
<button>
Hilfs-Zähler erhöhen
</button>
</div>
);
}
export default UseMemoExampleTwo;
Da wir mit einer Liste von Zahlen starten und bereits unsere Funktion für die Ermittlung der größten Zahl haben, können wir die Funktion verwenden, um bereits eine Ausgabe hinzukriegen.
Ebenfalls können wir den aktuellen Zustand des helperCounter
ausgeben. Diese Info ist nicht von größter Bedeutung, aber vollständigskeitshalber machen wir das.
import { useState } from 'react';
function UseMemoExampleTwo() {
const [nums, setNums] = useState([1, 5, 2, 8, 3]);
const [helperCounter, setHelperCounter] = useState(0);
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
};
const biggestNumber = findBiggestNumber(nums);
return (
<div>
<p>Die größte Zahl ist: {biggestNumber}</p>
<hr />
<button>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: {helperCounter}</p>
<button>
Hilfs-Zähler erhöhen
</button>
</div>
);
}
export default UseMemoExampleTwo;
In diesem Fall, wenn die Komponente gerendert wird, wird die größte ermittelte Zahl ausgegeben.
Nun kümmern wir uns um den Button “Neue Zahl generieren”. Dieser braucht irgendeinen Handler, welcher ausgeführt wird, wenn man drauf klickt. Dieser Handler wird eine neue Zahl generieren und über die Update-Funktion setNums
den Wert von nums
aktualisieren. Da in diesem Fall die Komponente neu gerendert wird, erhalten wir ebenfalls eine neue höchste Zahl, unter Umständen. Es kann auch eine Zahl generiert werden, die kleiner als die aktuelle Höchstzahl. In diesem Fall bleibt die höchste Zahl gleich, aber es findet dennoch ein Re-Rendering statt, da sich die Liste der Zahlen geändert hat.
import { useState } from 'react';
function UseMemoExampleTwo() {
const [nums, setNums] = useState([1, 5, 2, 8, 3]);
const [helperCounter, setHelperCounter] = useState(0);
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
};
const biggestNumber = findBiggestNumber(nums);
const addNewNum = () => {
setNums(currentNums => [...currentNums, Math.floor(Math.random() * 100)]);
};
return (
<div>
<p>Die größte Zahl ist: {biggestNumber}</p>
<hr />
<button onClick={addNewNum}>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: {helperCounter}</p>
<button>
Hilfs-Zähler erhöhen
</button>
</div>
);
}
export default UseMemoExampleTwo;
Anschließend fügen wir auch einen Handler für unseren Hilfsbutton, damit wir kontrolliert die Komponente aktualisieren können.
import { useState } from 'react';
function UseMemoExampleTwo() {
const [nums, setNums] = useState([1, 5, 2, 8, 3]);
const [helperCounter, setHelperCounter] = useState(0);
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
};
const biggestNumber = findBiggestNumber(nums);
const addNewNum = () => {
setNums(currentNums => [...currentNums, Math.floor(Math.random() * 100)]);
};
const changeHelperCounter = () => {
setHelperCounter(currentCounter => currentCounter + 1);
};
return (
<div>
<p>Die größte Zahl ist: {biggestNumber}</p>
<hr />
<button onClick={addNewNum}>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: {helperCounter}</p>
<button onClick={changeHelperCounter}>
Hilfs-Zähler erhöhen
</button>
</div>
);
}
export default UseMemoExampleTwo;
Damit haben wir unsere Component fertig. Welche Verhalten können wir hier beobachten? Wenn wir eine neue Zahl generieren, ist es klar, dass sich die Komponente neu rendert, weil ein Wert, welcher von useState()
überwacht wird, ändert. Allerdings rendert sich die Komponente auch dann neu, wenn wir nur unseren Hilfs-Counter ändern. Wir erhalten in der Konsole im Browser nach jedem Klick auf den Button “Hilfs-Zähler erhöhen” einen Log “findBiggestNumber wird ausgeführt”. Dabei ist es bei Änderung des Hilfs-Zählers gar nicht notwendig, dass die Funktion erneut ausgeführt wird.
Nun schreiben wir unser Beispiel so um, dass wir useMemo()
verwenden und damit entsprechend das Component optimieren.
Dabei übergeben wir als nums
in diesem Falls als eine Abhängigkeit als zweiten Parameter an die useMemo()
Funktion.
const findBiggestNumber = (nums) => {
console.log('findBiggestNumber wird ausgeführt');
for (let i = 0; i < 1000000; i++) {}
return Math.max(...nums);
}
const [nums, setNums] = useState([1, 5, 2, 8, 3]);
const [helperCounter, setHelperCounter] = useState(0);
const biggestNumber = useMemo(() => {
return findBiggestNumber(nums);
}, [nums]);
const addNewNum = () => {
setNums(currentNums => [...currentNums, Math.floor(Math.random() * 100)]);
};
const increaseHelperCounter = () => {
setHelperCounter(currentCounter => currentCounter + 1);
};
return (
<div>
<p>Die größte Zahl ist: {biggestNumber}</p>
<hr />
<button onClick={addNewNum}>
Neue Zahl hinzufügen
</button>
<p>Hilfs-Zähler: {helperCounter}</p>
<button onClick={increaseHelperCounter}>
Hilfs-Zähler erhöhen
</button>
</div>
);
export default UseMemoExampleTwo;
Beispiel 3
Im dritten Beispiel möchte ich die Funktionsweise von useMemo()
nochmals verdeutlichen.
Was ist in diesem Beispiel gegeben? Ich möchte, dass eine bestimmte Berechnung (Funktion) nur dann ausgeführt wird, wenn von mir eingegebene Zahl in einem Eingabefeld von der letzten Zahl unterscheided. Die Zahlen werden dabei alle gesammelt und ausgegeben. Ebenfalls werden alle Ausführungen der komplexeren Funktion erfasst und ausgegeben.
Wir werden also zwei, von der Länge her identische Arrays/Listen haben. Die Zahlen-Liste enthält einfach alle Zahlen, welche über das Eingabefeld übermittelt werden. Die Ausführungsliste beinhaltet Folgendes:
- Wird die Berechnungsfunktion ausgeführt, wird hier die übermittelte Zahl hineinschrieben.
- Ist die übermittelte Zahl identisch mit der vorherigen, wird die Berechnungsfunktion nicht ausgeführt und es wird ein Bindestrich ”-” hineingeschrieben.
import { useMemo, useState, useRef } from 'react';
function UseMemoExampleThree() {
const [counter, setCounter] = useState(0);
const [counterValues, setCounterValues] = useState([]);
const [memoRunValues, setMemoRunValues] = useState([]);
const [actionMessage, setActionMessage] = useState('');
const refCounterInput = useRef(null);
useMemo(() => {
setActionMessage('Beginne Berechnung ...');
setTimeout(() => {
setActionMessage('Berechnung abgeschlossen!');
}, 1000);
}, [counter]);
const handleSetCounter = () => {
if (refCounterInput.current) {
setCounter(current => Number(refCounterInput.current.value));
setCounterValues(current => [...current, refCounterInput.current.value]);
if (counter !== Number(refCounterInput.current.value)) {
setMemoRunValues(current => [...current, Number(refCounterInput.current.value)]);
} else {
setMemoRunValues(current => [...current, '-']);
}
} else {
setCounter(current => current);
}
};
return (
<div>
{(actionMessage && actionMessage.length > 0) && (
<>
<p>{actionMessage}</p>
<hr />
</>
)}
<p>Zähler: {counter}</p>
<p>
<input
type="number"
ref={refCounterInput}
/>
</p>
<button onClick={handleSetCounter}>
Aktualisiere Zähler
</button>
<hr />
<div>
{counterValues.map((cValue, index) => (
<span key={index}>
[{cValue}]{index < counterValues.length - 1 ? ', ' : ''}
</span>
))}
</div>
<div>
{memoRunValues.map((mValue, index) => (
<span key={index}>
[{mValue}]{index < memoRunValues.length - 1 ? ', ' : ''}
</span>
))}
</div>
</div>
);
}
export default UseMemoExampleThree;
Als Ergebnis haben wir eine App, bei der wir eine Zahl in das Eingabefeld eingeben und übermitteln können. Alle übermittelten Zahlen werden erfasst und ausgegeben. Ändert sich die übermittelte Zahl nicht, sehen wir, dass keine Berechnung (Aufruf der komplexeren Funktion) dank useMemo()
und der Abhängigkeit counter
stattfindet.