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

后端 未结 4 1787
半阙折子戏
半阙折子戏 2020-12-02 04:40

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

I passed inputs - as a second argument to useCallback - non-ever-changea

相关标签:
4条回答
  • 2020-12-02 05:09

    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!

    0 讨论(0)
  • 2020-12-02 05:14

    One-liner for useCallback vs useMemo:

    useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).


    With useCallback you memoize functions, useMemo memoizes any computed value:

    const fn = () => 42 // assuming expensive calculation here
    const memoFn = useCallback(fn, [dep]) // (1)
    const memoFnReturn = useMemo(fn, [dep]) // (2)
    

    (1) will return a memoized version of fn - same reference across multiple renders, as long as dep is the same. But every time you invoke memoFn, that complex computation starts again.

    (2) will invoke fn every time dep changes and remember its returned value (42 here), which is then stored in memoFnReturn.

    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>

    0 讨论(0)
  • 2020-12-02 05:20

    useMemo and useCallback use memoization.

    I like to think of memoization as remembering something.

    While both useMemo and useCallback remember something between renders until the dependancies change, the difference is just what they remember.

    useMemo will remember the returned value from your function.

    useCallback will remember your actual function.

    Source: What is the difference between useMemo and useCallback?

    0 讨论(0)
  • 2020-12-02 05:31

    You are calling the memoized callback every time, when you do:

    const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
    const computedCallback = calcCallback();
    

    This is why the count of useCallback is going up. However the function never changes, it never *****creates**** a new callback, its always the same. Meaning useCallback is correctly doing it's job.

    Let's making some changes in your code to see this is true. Let's create a global variable, lastComputedCallback, that will keep track of if a new (different) function is returned. If a new function is returned, that means useCallback just "executed again". So when it executes again we will call expensiveCalc('useCallback'), as this is how you are counting if useCallback did work. I do this in the code below, and it is now clear that useCallback is memoizing as expected.

    If you want to see useCallback re-create the function everytime, then uncomment the line in the array that passes second. You will see it re-create the function.

    '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                                                                     
    0 讨论(0)
提交回复
热议问题