Vielleicht habe ich etwas falsch verstanden, aber useCallback Hook wird jedes Mal ausgeführt, wenn ein erneutes Rendern erfolgt.
Ich habe Eingaben übergeben - als zweites Argument für useCallback - nicht ständig änderbare Konstanten -, aber der zurückgegebene Rückruf führt immer noch meine teuren Berechnungen bei jedem Rendern aus (ich bin mir ziemlich sicher - Sie können dies im folgenden Snippet selbst überprüfen).
Ich habe useCallback in useMemo geändert - und useMemo funktioniert wie erwartet - wird ausgeführt, wenn sich übergebene Eingaben ändern. Und merkt sich wirklich die teuren Berechnungen.
Live-Beispiel:
'use strict';
const { useState, useCallback, useMemo } = React;
const neverChange = 'I never change';
const oneSecond = 1000;
function App() {
const [second, setSecond] = useState(0);
// This 👇 expensive function executes everytime when render happens:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
// This 👇 executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${computedCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}
const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };
function expensiveCalc(hook) {
let i = 0;
while (i < tenThousand) i++;
return ++expensiveCalcExecutedTimes[hook];
}
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
<h1>useCallback vs useMemo:</h1>
<div id="app">Loading...</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
computedCallback = calcCallback();
.computedCallback
sollte nur sein = calcCallback, it will update the callback once
neverChange` ändert sich.Antworten:
TL; DR;
useMemo
dient zum Speichern eines Berechnungsergebnisses zwischen den Aufrufen einer Funktion und zwischen RendernuseCallback
besteht darin, einen Rückruf selbst (referenzielle Gleichheit) zwischen Renderings zu speichernuseRef
dient dazu, Daten zwischen den Renderings zu speichern (beim Aktualisieren wird kein erneutes Rendern ausgelöst)useState
dient dazu, Daten zwischen den Renderings zu speichern (beim Aktualisieren wird ein erneutes Rendern ausgelöst).Lange Version:
useMemo
konzentriert sich auf die Vermeidung schwerer Berechnungen.useCallback
konzentriert sich auf eine andere Sache: Es behebt Leistungsprobleme, wenn Inline-Ereignishandler wieonClick={() => { doSomething(...); }
dasPureComponent
erneute Rendern von Kindern verursachen (da Funktionsausdrücke dort jedes Mal referenziell unterschiedlich sind).Dies
useCallback
istuseRef
eher eine Möglichkeit, sich ein Berechnungsergebnis zu merken, als eine Möglichkeit, es sich zu merken.Wenn ich mir die Dokumente anschaue , stimme ich zu, dass es dort verwirrend aussieht.
Beispiel
Angenommen, wir haben ein
PureComponent
Kind auf Basis<Pure />
, das erst dann neu gerendert wird, wennprops
es geändert wird.Dieser Code rendert das Kind jedes Mal neu, wenn das Elternteil neu gerendert wird - da die Inline-Funktion jedes Mal referenziell unterschiedlich ist:
function Parent({ ... }) { const [a, setA] = useState(0); ... return ( ... <Pure onChange={() => { doSomething(a); }} /> ); }
Wir können das mit Hilfe von
useCallback
:function Parent({ ... }) { const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, []); ... return ( ... <Pure onChange={onPureChange} /> ); }
Sobald dies
a
geändert wurde, stellen wir fest, dass die vononPureChange
uns erstellte Handlerfunktion - und React für uns in Erinnerung geblieben - immer noch auf den altena
Wert verweist ! Wir haben einen Fehler anstelle eines Leistungsproblems! Dies liegt daran, dassonPureChange
ein Abschluss verwendet wird, um auf diea
Variable zuzugreifen , die beimonPureChange
Deklarieren erfasst wurde. Um dies zu beheben, müssen wir React wissen lassen, woonPureChange
eine neue Version abgelegt und neu erstellt / gespeichert (gespeichert) werden soll, die auf die richtigen Daten verweist. Wir tun dies, indem wira
als Abhängigkeit im zweiten Argument zu `useCallback hinzufügen:const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Wenn dies
a
geändert wird, rendert React die Komponente neu. Beim erneuten Rendern wird festgestellt, dass die Abhängigkeit füronPureChange
unterschiedlich ist und eine neue Version des Rückrufs neu erstellt / gespeichert werden muss. Endlich funktioniert alles!quelle
Sie rufen den gespeicherten Rückruf jedes Mal auf, wenn Sie Folgendes tun:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]); const computedCallback = calcCallback();
Deshalb
useCallback
steigt die Zahl der . Die Funktion ändert sich jedoch nie, sie erzeugt niemals einen neuen Rückruf, sie ist immer dieselbe. DasuseCallback
heißt, es macht seinen Job richtig.Nehmen wir einige Änderungen an Ihrem Code vor, um festzustellen, ob dies der Fall ist. Erstellen wir eine globale Variable, die verfolgt
lastComputedCallback
, ob eine neue (andere) Funktion zurückgegeben wird. Wenn eine neue Funktion zurückgegeben wird, bedeutet dies, dassuseCallback
nur "erneut ausgeführt" wird. Wenn es erneut ausgeführt wird, rufen wir anexpensiveCalc('useCallback')
, da Sie auf diese Weise zählen, obuseCallback
es funktioniert hat. Ich mache dies im Code unten, und es ist jetzt klar, dassuseCallback
das Auswendiglernen wie erwartet ist.Wenn Sie möchten,
useCallback
dass die Funktion jedes Mal neu erstellt wird, kommentieren Sie die Zeile im übergebenen Array aussecond
. Sie werden sehen, dass die Funktion neu erstellt wird.'use strict'; const { useState, useCallback, useMemo } = React; const neverChange = 'I never change'; const oneSecond = 1000; let lastComputedCallback; function App() { const [second, setSecond] = useState(0); // This 👇 is not expensive, and it will execute every render, this is fine, creating a function every render is about as cheap as setting a variable to true every render. const computedCallback = useCallback(() => expensiveCalc('useCallback'), [ neverChange, // second // uncomment this to make it return a new callback every second ]); if (computedCallback !== lastComputedCallback) { lastComputedCallback = computedCallback // This 👇 executes everytime computedCallback is changed. Running this callback is expensive, that is true. computedCallback(); } // This 👇 executes once const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]); setTimeout(() => setSecond(second + 1), oneSecond); return ` useCallback: ${expensiveCalcExecutedTimes.useCallback} times | useMemo: ${computedMemo} | App lifetime: ${second}sec. `; } const tenThousand = 10 * 1000; let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 }; function expensiveCalc(hook) { let i = 0; while (i < 10000) i++; return ++expensiveCalcExecutedTimes[hook]; } ReactDOM.render( React.createElement(App), document.querySelector('#app') );
<h1>useCallback vs useMemo:</h1> <div id="app">Loading...</div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
Vorteil der
useCallback
ist , dass die zurückgegebene Funktion ist die gleiche, so reagieren nichtremoveEventListener
‚ing undaddEventListener
ing auf dem Element jedes Mal, sofern diecomputedCallback
Änderungen. Und dascomputedCallback
ändert sich nur, wenn sich die Variablen ändern. Somit wird nuraddEventListener
einmal reagieren .Tolle Frage, ich habe viel gelernt, indem ich sie beantwortet habe.
quelle
addEventListener/removeEventListener
(diese Operation selbst ist nicht schwer, da sie nicht zu einem DOM-Reflow / Repaint führt), sondern zu vermeiden, dass ein Kind, das diesen Rückruf verwendet , erneut gerendert wirdPureComponent
(oder mit benutzerdefiniertenshouldComponentUpdate()
)*EventListener
, billig zu sein, das ist ein großartiger Punkt, der keinen Reflow / Lack verursacht! Ich fand es immer teuer und versuchte es zu vermeiden. Wenn ich also nicht zu a übergehePureComponent
, ist die Komplexität, die durchuseCallback
den Kompromiss zwischen Reaktion und DOM hinzugefügt wird , zusätzliche Komplexität wertremove/addEventListener
?PureComponent
oder angepasst werden, wird kein Wert hinzugefügt (Overhead durch zusätzliche Überprüfung auf das zweite Argument macht das Überspringen eines zusätzlichenshouldComponentUpdate
useCallback
useCallback
removeEventListener/addEventListener
*EventListener
es für mich keine teure Operation ist.Einzeiler für
useCallback
vsuseMemo
:Mit Ihren Memoize-
useCallback
FunktionenuseMemo
merkt man sich jeden berechneten Wert:const fn = () => 42 // assuming expensive calculation here const memoFn = useCallback(fn, [dep]) // (1) const memoFnReturn = useMemo(fn, [dep]) // (2)
(1)
gibt eine gespeicherte Version vonfn
- dieselbe Referenz über mehrere Renderings zurück, solangedep
dieselbe identisch ist. Aber jedes Mal , wenn Sie aufrufenmemoFn
, beginnt diese komplexe Berechnung erneut.(2)
wird beifn
jederdep
Änderung aufgerufen und merkt sich den zurückgegebenen Wert (42
hier), der dann in gespeichert wirdmemoFnReturn
.Code-Snippet anzeigen
const App = () => { const [dep, setDep] = useState(0); const fn = () => 42 + dep; // assuming expensive calculation here const memoFn = useCallback(fn, [dep]); // (1) const memoFnReturn = useMemo(fn, [dep]); // (2) return ( <div> <p> memoFn is {typeof memoFn} </p> <p> Every call starts new calculation, e.g. {memoFn()} {memoFn()} </p> <p>memoFnReturn is {memoFnReturn}</p> <p> Only one calculation for same dep, e.g. {memoFnReturn} {memoFnReturn} </p> <button onClick={() => setDep((p) => p + 1)}>Change dep</button> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef, useCallback, useMemo } = React</script>
quelle