Talvez eu tenha entendido mal alguma coisa, mas useCallback Hook é executado sempre que ocorre uma nova renderização.
Passei entradas - como um segundo argumento para usarCallback - constantes não sempre mutáveis - mas o retorno de chamada memoized ainda executa meus cálculos caros em cada renderização (tenho certeza - você pode verificar por si mesmo no trecho abaixo).
Eu mudei useCallback para useMemo - e useMemo funciona conforme o esperado - é executado quando as entradas são alteradas. E realmente memoriza os cálculos caros.
Exemplo ao 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();
.computedCallback
deve ser apenas = calcCallback, it will update the callback once
neverChange` alterações.Respostas:
TL; DR;
useMemo
é memorizar um resultado de cálculo entre as chamadas de uma função e entre renderizaçõesuseCallback
é memorizar o próprio retorno de chamada (igualdade referencial) entre renderizaçõesuseRef
é manter os dados entre as renderizações (a atualização não dispara uma nova renderização)useState
é manter os dados entre as renderizações (a atualização irá disparar uma nova renderização)Versão longa:
useMemo
concentra-se em evitar cálculos pesados.useCallback
concentra-se em uma coisa diferente: corrige problemas de desempenho quando manipuladores de eventos em linha comoonClick={() => { doSomething(...); }
causaPureComponent
a re-renderização do filho (porque as expressões de função são referencialmente diferentes a cada vez)Dito isso,
useCallback
está mais perto deuseRef
, em vez de uma forma de memorizar um resultado de cálculo.Olhando para os documentos, eu concordo que parece confuso lá.
Exemplo
Suponha que tenhamos um
PureComponent
filho com base em<Pure />
que seria renderizado novamente apenas depois deprops
alterado.Este código renderiza novamente o filho cada vez que o pai é renderizado novamente - porque a função inline é referencialmente diferente a cada vez:
function Parent({ ... }) { const [a, setA] = useState(0); ... return ( ... <Pure onChange={() => { doSomething(a); }} /> ); }
Podemos lidar com isso com a ajuda de
useCallback
:function Parent({ ... }) { const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, []); ... return ( ... <Pure onChange={onPureChange} /> ); }
Mas uma vez que
a
é alterada, descobrimos que aonPureChange
função de manipulador que criamos - e React lembrou para nós - ainda aponta para oa
valor antigo ! Temos um bug em vez de um problema de desempenho! Isso ocorre porqueonPureChange
usa um fechamento para acessar aa
variável, que foi capturada quandoonPureChange
foi declarada. Para corrigir isso, precisamos informar ao React onde soltaronPureChange
e recriar / lembrar (memorizar) uma nova versão que aponta para os dados corretos. Fazemos isso adicionandoa
como uma dependência no segundo argumento para `useCallback:const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Agora, se
a
for alterado, o React renderiza novamente o componente. E durante a re-renderização, ele vê que a dependência paraonPureChange
é diferente e há uma necessidade de recriar / memorizar uma nova versão do retorno de chamada. Finalmente tudo funciona!NB não apenas para
PureComponent
/React.memo
, igualdade referencial pode ser crítica quando se usa algo como uma dependência emuseEffect
.fonte
One-liner para
useCallback
vsuseMemo
:Com
useCallback
suas funções memoize,useMemo
memoriza qualquer valor calculado:const fn = () => 42 // assuming expensive calculation here const memoFn = useCallback(fn, [dep]) // (1) const memoFnReturn = useMemo(fn, [dep]) // (2)
(1)
retornará uma versão memoized defn
- mesma referência em vários renderizadores, desde quedep
seja o mesmo. Mas cada vez que você invocamemoFn
, esse cálculo complexo começa novamente.(2)
irá invocarfn
toda vez quedep
mudar e lembrar de seu valor retornado (42
aqui), que é então armazenado emmemoFnReturn
.Exibir trecho 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>
fonte
Você está chamando o retorno de chamada memoized todas as vezes, ao fazer:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]); const computedCallback = calcCallback();
É por isso que a contagem de
useCallback
está aumentando. No entanto, a função nunca muda, ela nunca cria **** um novo retorno de chamada, é sempre o mesmo. SignificadouseCallback
está fazendo seu trabalho corretamente.Vamos fazer algumas alterações em seu código para ver se isso é verdade. Vamos criar uma variável global,,
lastComputedCallback
que controlará se uma nova função (diferente) for retornada. Se uma nova função for retornada, isso significauseCallback
apenas "executado novamente". Então quando for executado novamente a gente chamaexpensiveCalc('useCallback')
, pois é assim que você está contando seuseCallback
funcionou. Eu faço isso no código abaixo e agora está claro queuseCallback
está memoizing conforme o esperado.Se você quiser
useCallback
recriar a função todas as vezes, descomente a linha no array que passasecond
. Você o verá recriar a função.'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>
Benefício de
useCallback
é que a função retornado é o mesmo, então reagir não éremoveEventListener
'ing eaddEventListener
ing no elemento cada vez, a menos que ascomputedCallback
alterações. E ascomputedCallback
únicas mudanças quando as variáveis mudam. Assim, reagirá apenasaddEventListener
uma vez.Ótima pergunta, aprendi muito respondendo-a.
fonte
addEventListener/removeEventListener
(este op em si não é pesado, pois não leva ao refluxo / repintura do DOM), mas para evitar a re-renderizaçãoPureComponent
(ou com personalizadoshouldComponentUpdate()
) filho que usa esse retorno de chamada*EventListener
ser barato, isso é um grande ponto sobre isso não causar refluxo / pintura! Sempre pensei que era caro, então tentei evitar. Portanto, no caso de eu não estar passando para aPureComponent
, a complexidade adicionadauseCallback
vale a pena compensar por ter o react e o DOM fazendo complexidade extraremove/addEventListener
?PureComponent
ou customizarshouldComponentUpdate
para componentes aninhados, entãouseCallback
não adicionará nenhum valor (a sobrecarga pela verificação extra do segundouseCallback
argumento anulará o salto deremoveEventListener/addEventListener
movimento extra )*EventListener
não é uma operação cara para mim.useMemo
euseCallback
usar memoização.Gosto de pensar em memoização como a lembrança de algo .
Enquanto ambos
useMemo
euseCallback
lembram de algo entre renderizações até que as dependências mudem, a diferença é exatamente o que eles lembram .useMemo
irá lembrar o valor retornado de sua função.useCallback
vai se lembrar de sua função real.Fonte: Qual é a diferença entre useMemo e useCallback?
fonte