preface

Before rex hooks were added to React, we could implement hooks with PureComponent, shouldComponentUpdate, etc. The answer is yes. Let’s answer them one by one.

I. Interpretation of official documents

The following is the explanation of the official document. I feel some precious words like gold. After reading it, I still have a feeling that I cannot find the north.

See the link useCallback

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a,b)
  },
  [a, b]
)
Copy the code

Returns a Memoized callback function.

Passing the inline callback function and the array of dependencies as arguments to useCallback returns the Memoized version of the callback function, which is updated only when a dependency changes. This is useful when you pass callback data to child components that are optimized and use reference equality to avoid unnecessary rendering (such as shouldComponentUpdate).

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

Pay attention to

The dependency array is not passed as an argument to the callback function. Conceptually, though, it looks like this: all values referenced in callback functions should appear in dependency arrays. In the future, compilers will get smarter and it will be possible to create arrays automatically.

We recommend enabling the Strict-deps rule in eslint-plugin-react-hooks. This rule warns when you add false dependencies and suggests how to fix them.

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
Copy the code

Returns a memoized value.

You pass in the create function and the dependency array as arguments to useMemo, which recalculates memoized values only when a dependency changes. This optimization helps avoid costly calculations every time you render.

Remember that functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function. Operations such as side effects are used by useEffect, not useMemo.

If the dependency array is not provided, useMemo evaluates the new value each time it renders.

You can use useMemo as a means of performance optimization, but don’t use it as a semantic guarantee. In the future, React might choose to “forget” some of the previous Memoized values and recalculate them at the next rendering, such as freeing memory for off-screen components. Write code that can run without useMemo first — then add useMemo to your code to optimize performance.

Pay attention to

Dependency arrays are not passed as arguments to the Create function. Conceptually, though, it looks like this: all values referenced in the create function should appear in the dependency array. In the future, compilers will get smarter and it will be possible to create arrays automatically.

We recommend enabling the Strict-deps rule in eslint-plugin-react-hooks. This rule warns when you add false dependencies and suggests how to fix them.

After reading the official explanation of useCallback and useMemo, you can probably summarize the following points:

  1. useCallbackTo receive aInline callback functionsAnd aDependency array
  2. useMemoTo receive aCreate a functionAnd aDependency array
  3. useCallbackanduseMemoAre updated when a dependency changes
  4. They are all performance optimization methods
  5. useMemoUsed where there is a lot of computation

The paper come zhongjue shallow, must know this to practice, let’s write an example

Ii. Basic usage scenarios and comparison

useCallbackThe use of

UseCallback receives an inline callback function and an array of dependencies that are updated when the dependencies change.

1. Not useduseCallback

This example consists of two components,
and

function App () { const [count, setCount] = useState(0) const [name, setName] = useState('fruit') const changeCount = () => { setCount(count + 1) } const changeName = () => { setName(name +  1) } return ( <div> <div>Count is {count}</div> <div>Name is {name}</div> <br/> <div> <button onClick={changeName}>Change Name</button> <Child changeCount={changeCount} /> </div> </div> ) } function Child({changeCount}) { console.log('Child render') return <div> <button onClick={changeCount}>Increment Count</button> </div> } render(<App />, document.getElementById('root'))Copy the code

Stackblitz.com/edit/react-…You can try it out with this online editorAs shown above, when we clickChange NameandIncrement CountBoth buttons cause,ChildComponent re-rendering execution. There’s a lot of overhead. When you clickChange NameWhen, has not changedIncrement Count, but it still causes the child component to re-execute.

2. BymemoTo optimize the<Child />component

Before practice is to use componentWillReceiveProps, shouldComponentUpdate, pureComponent for performance optimization. In hooks we use memo for optimization.

Alter
as follows:

const Child = memo(({changeCount}) => { console.log('Child render') return <div> <button onClick={changeCount}>Increment  Count</button> </div> })Copy the code

But when we click Change Name and Increment Count, it doesn’t seem to work.

The reason? When we pass changeCount as props to other components, optimizations like PureComponent, shouldComponentUpdate, react. memo, etc. fail (because it’s a different function each time).

When we pass changeCount, every time we click the button, the function will be re-executed and a new changeCount will be created. The pointer address will be different each time, so the comparison will be different each time, so we also need to solve this problem by using useCallback.

The memo is similar to PureComponent, the function props does a shallow comparison, but useMemo is used to cache values, so they are different.

3. useCallbackplay

function App () { const [count, setCount] = useState(0) const [name, Const changeCount = useCallback(() => {setCount(count + 1)}, [count]) const changeName = () => { setName(name + 1) } return ( <div> <div>Count is {count}</div> <div>Name is {name}</div> <br/> <div> <button onClick={changeName}>Change Name</button> <Child changeCount={changeCount} /> </div> </div> ) } const Child = memo(({changeCount}) => { console.log('Child render') return <div> <button onClick={changeCount}>Increment Count</button> </div> })Copy the code

Stackblitz.com/edit/react-… You can experiment here by saying that the Child executes normally when Increment Count is clicked and stops executing when Change Name is clicked. This is the goal of optimization.

We can try to wrap function with a useCallback layer.

Therefore, you can use memo + useCallback to avoid being updated. But it also takes up some memory.

useMemoThe use of

UseMemo and useCallback are almost identical in use, but useMemo is used to cache values.

UseMemo is generally used for large caches with intensive computations. The useMemo dependency allows us to save memory by performing calculations only when the specified variable value changes.

Const usePrevProps = value => {const ref = react.useref (); // Get the props const usePrevProps = value => {const ref = react.useref (); React.useEffect(() => { ref.current = value; }); return ref.current; } function App() { const [count, setCount] = React.useState(0); const [total, setTotal] = React.useState(0); Const calcValue = react.usememo (() => {return Array(100000).fill(").map(v => /* some large calculations */ v); }, [count]); const handleCount = () => setCount(count => count + 1); const handleTotal = () => setTotal(total + 1); const prevCalcValue = usePrevProps(calcValue); Console. log(' prevCalcValue === calcValue '); return ( <div> <div>Count is {count}</div> <div>Total is {total}</div> <br/> <div> <button onClick={handleCount}>Increment Count</button> <button onClick={handleTotal}>Increment Total</button> </div> </div> ) } ReactDOM.render(<App />, document.body)Copy the code

This time we’ll focus on this line, where useMemo’s first function is executed only if the value of the count variable changes.

Const calcValue = react.usememo (() => {return Array(100000).fill(").map(v => /* some large calculations */ v); }, [count])Copy the code

The useMemo dependency allows us to save memory by performing calculations only when the specified variable value changes.

UseMemo passes the create function to useMemo as an array of dependencies. Similar to useEffect, changes to the passed function only depend on the recalcuation and re-execution of the function when the values in the passed array of dependencies change. This optimization helps avoid costly calculations every time you render. As with the second parameter of useEffect, it is evaluated only when the value of the second parameter changes.

Three, source code analysis

// useCallback function updateCallback(callback, deps) { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState ! == null) { if (nextDeps ! == null) { const prevDeps = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } hook.memoizedState = [callback, nextDeps]; return callback; } // useMemo function updateMemo(nextCreate, deps) { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState ! == null) { if (nextDeps ! == null) { const prevDeps = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; }Copy the code

As you can see from the above code, useMemo and useCallback are almost the same. Once we understand useCallback, it is very easy to understand useMemo. The only difference is that useCallback caches the first parameter, callback, according to the dependency (DEPS). UseMemo caches the value of the first parameter callback after execution based on the dependencies (DEPS).

UseCallback will re-create a function body, whereas useMemo will re-create a function body when num changes.

reflection

useuseMemoanduseCallbackCan it achieve the goal of optimization?

The answer is no.

  1. useMemoanduseCallbackThey can’t be used blindly because they are implemented based on closures, which can eat up memory.
  2. Consider when dependencies change frequentlyUseMemo, useCallbackIs it cost-effective becauseuseCallbackFunction bodies are created frequently.useMemoCallbacks are created frequently.

conclusion

  1. useMemoanduseCallbackCan be used to solve performance problems
  2. useCallbackNeed to bememoTogether with
  3. useMemowithmemoIt’s totally different
  4. useMemoanduseCallbackThe source code is almost the same,useCallbackReturns a callback function,useMemoThe value is returned
  5. useMemoGenerally used in the case of a large amount of calculation
  6. useMemoanduseCallbackIt may not achieve the purpose of performance optimization, so it cannot be used blindly

Write here, welcome to correct mistakes…