Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.5k views
in Technique[技术] by (71.8m points)

reactjs - What's the difference between useCallback and useMemo in practice?

Maybe I misunderstood something, but useCallback Hook runs everytime when re-render happens.

I passed inputs - as a second argument to useCallback - non-ever-changeable constants - but returned memoized callback still runs my expensive calculations at every render (I'm pretty sure - you can check by yourself in the snippet below).

I've changed useCallback to useMemo - and useMemo works as expected — runs when passed inputs changes. And really memoizes the expensive calculations.

Live example:

'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>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

TL;DR;

  • useMemo is to memoize a calculation result between a function's calls and between renders
  • useCallback is to memoize a callback itself (referential equality) between renders
  • useRef is to keep data between renders (updating does not fire re-rendering)
  • useState is to keep data between renders (updating will fire re-rendering)

Long version:

useMemo focuses on avoiding heavy calculation.

useCallback focuses on a different thing: it fixes performance issues when inline event handlers like onClick={() => { doSomething(...); } cause PureComponent child re-rendering (because function expressions there are referentially different each time)

This said, useCallback is closer to useRef, rather than a way to memoize a calculation result.

Looking into the docs I do agree it looks confusing there.

useCallback will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

Example

Suppose we have a PureComponent-based child <Pure /> that would re-render only once its props are changed.

This code re-renders the child each time the parent is re-rendered — because the inline function is referentially different each time:

function Parent({ ... }) {
  const [a, setA] = useState(0);
  ... 
  return (
    ...
    <Pure onChange={() => { doSomething(a); }} />
  );
}

We can handle that with the help of useCallback:

function Parent({ ... }) {
  const [a, setA] = useState(0);
  const onPureChange = useCallback(() => {doSomething(a);}, []);
  ... 
  return (
    ...
    <Pure onChange={onPureChange} />
  );
}

But once a is changed we find that the onPureChange handler function we created — and React remembered for us — still points to the old a value! We've got a bug instead of a performance issue! This is because onPureChange uses a closure to access the a variable, which was captured when onPureChange was declared. To fix this we need to let React know where to drop onPureChange and re-create/remember (memoize) a new version that points to the correct data. We do so by adding a as a dependency in the second argument to `useCallback :

const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, [a]);

Now, if a is changed, React re-renders the component. And during re-render, it sees that the dependency for onPureChange is different, and there is a need to re-create/memoize a new version of the callback. Finally everything works!

NB not just for PureComponent/React.memo, referential equality may be critical when use something as a dependency in useEffect.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...