Tal vez no entendí algo, pero useCallback Hook se ejecuta cada vez que se vuelve a renderizar.
Pasé entradas, como segundo argumento para useCallback, constantes que no se cambian siempre, pero la devolución de llamada memorizada aún ejecuta mis costosos cálculos en cada render (estoy bastante seguro, puede verificarlo usted mismo en el fragmento a continuación).
He cambiado useCallback a useMemo, y useMemo funciona como se esperaba, se ejecuta cuando las entradas pasadas cambian. Y realmente memoriza los costosos cálculos.
Ejemplo en vivo:
'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();.computedCallbacksolo debería ser = calcCallback, it will update the callback onceneverChange` cambia.Respuestas:
TL; DR;
useMemoes memorizar el resultado de un cálculo entre las llamadas de una función y entre los rendersuseCallbackes memorizar una devolución de llamada en sí (igualdad referencial) entre rendersuseRefes mantener los datos entre renderizados (la actualización no dispara el re-renderizado)useStatees mantener los datos entre renderizados (la actualización activará el re-renderizado)Versión larga:
useMemose centra en evitar cálculos pesados.useCallbackse centra en una cosa diferente: soluciona problemas de rendimiento cuando los controladores de eventos en línea comoonClick={() => { doSomething(...); }causanPureComponentla re-renderización de los niños (porque las expresiones de función son referencialmente diferentes cada vez)Dicho esto,
useCallbackestá más cercauseRefque una forma de memorizar el resultado de un cálculo.Mirando en el documentos, estoy de acuerdo en que parece confuso allí.
Ejemplo
Supongamos que tenemos un
PureComponenthijo basado<Pure />se volvería a representar solo una vez quepropsse cambia.Este código vuelve a renderizar al hijo cada vez que se vuelve a renderizar al padre, porque la función en línea es referencialmente diferente cada vez:
function Parent({ ... }) { const [a, setA] = useState(0); ... return ( ... <Pure onChange={() => { doSomething(a); }} /> ); }Podemos manejar eso con la ayuda de
useCallback:function Parent({ ... }) { const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, []); ... return ( ... <Pure onChange={onPureChange} /> ); }Pero una vez que
ase cambia, encontramos que laonPureChangefunción de controlador que creamos, y React recordó por nosotros, ¡todavía apunta alavalor anterior! ¡Tenemos un error en lugar de un problema de rendimiento! Esto se debe a queonPureChangeutiliza un cierre para acceder a laavariable, que fue capturada cuandoonPureChangese declaró. Para solucionar este problema, debemos dejar que React sepa dónde colocaronPureChangey volver a crear / recordar (memorizar) una nueva versión que apunte a los datos correctos. Lo hacemos agregandoacomo dependencia en el segundo argumento a `useCallback:const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, [a]);Ahora, si
ase cambia, React vuelve a renderizar el componente. Y durante la re-renderización, ve que la dependencia deonPureChangees diferente, y es necesario volver a crear / memorizar una nueva versión de la devolución de llamada. ¡Finalmente todo funciona!fuente
Está llamando a la devolución de llamada memorizada cada vez, cuando lo hace:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]); const computedCallback = calcCallback();Es por eso que el conteo de
useCallbackestá aumentando. Sin embargo, la función nunca cambia, nunca crea **** una nueva devolución de llamada, siempre es la misma. El significadouseCallbackes hacer correctamente su trabajo.Hagamos algunos cambios en su código para ver que esto es cierto. Creemos una variable global
lastComputedCallback, que hará un seguimiento de si se devuelve una función nueva (diferente). Si se devuelve una nueva función, eso significa queuseCallbacksimplemente se "ejecuta de nuevo". Entonces, cuando se ejecute nuevamente, lo llamaremosexpensiveCalc('useCallback'), ya que así es como está contando siuseCallbackfuncionó. Hago esto en el código a continuación, y ahora está claro queuseCallbackestá memorizando como se esperaba.Si desea
useCallbackvolver a crear la función cada vez, descomente la línea en la matriz que pasasecond. Verá que vuelve a crear la función.'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>Beneficio de
useCallbackes que la función devuelta es la misma, por lo que no reaccioneremoveEventListener'ING yaddEventListenering en el elemento cada vez, a menos que elcomputedCallbackcambio. Y loscomputedCallbackúnicos cambios cuando cambian las variables. Así reaccionará soloaddEventListeneruna vez.Gran pregunta, aprendí mucho respondiéndola.
fuente
addEventListener/removeEventListener(esta operación en sí no es pesada, ya que no conduce al reflujo / repintado de DOM) sino a evitar volver a renderizarPureComponent(o personalizarshouldComponentUpdate()) al niño que usa esta devolución de llamada*EventListenerera barato, ¡es un gran punto que no cause reflujo / pintura! Siempre pensé que era caro, así que intenté evitarlo. Entonces, en el caso de que no pase a aPureComponent, ¿la complejidad agregadauseCallbackvale la pena el intercambio de tener que reaccionar y DOM hacer una complejidad adicionalremove/addEventListener?PureComponento no se personalizashouldComponentUpdatepara los componentes anidados, entoncesuseCallbackno agregará ningún valor (la sobrecarga mediante la verificación adicional del segundouseCallbackargumento anulará la omisión deremoveEventListener/addEventListenermovimiento adicional )*EventListenerno es una operación costosa para mí.Una línea para
useCallbackvsuseMemo:Con las
useCallbackfunciones de memorización,useMemomemoriza cualquier valor calculado:const fn = () => 42 // assuming expensive calculation here const memoFn = useCallback(fn, [dep]) // (1) const memoFnReturn = useMemo(fn, [dep]) // (2)(1)devolverá una versión memorizada de lafnmisma referencia en varias representaciones, siempre quedepsea la misma. Pero cada vez que invocamemoFn, ese complejo cálculo comienza de nuevo.(2)invocaráfncada vez quedepcambie y recordará su valor devuelto (42aquí), que luego se almacena enmemoFnReturn.Mostrar fragmento de código
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>fuente