useCallback and useMemo performance optimization
UseCallback analysis
UseCallback
The actual purpose of useCallback is to optimize performance.
What kind of optimization does useCallback perform?
For example, in the case of the counter below, when we click the button, the counter data will change, and the App function component will be rerendered, which means that the increment function will be redefined once, and every time the button is clicked, the increment function will be redefined;
Although the garbage collection mechanism will recycle the previous defined increment function every time the increment function is defined, this unnecessary repeated definition will affect performance.
import React, { memo, useState } from 'react' const App = memo(() => { const [counter, setCounter] = useState(10) function increment() { setCounter(counter + 1) } return ( <div> <h2>{counter}</h2> <button onClick={() => increment()}>+1</button> </div> ) }) export default App
How to optimize performance?
Calling useCallback will return a memoized (memorized) callback function;
When the dependency is unchanged, the returned callback function is the same when it is defined multiple times;
- Parameter 1: Pass in a callback function. If the dependency changes, a new callback function will be defined. If the dependency does not change, the original callback function will still be used.
- Parameter 2: For control of dependencies, the second parameter requires an array to be passed into, and dependencies can be passed into the array. Passing an empty array means there is no dependency
const memoizedCallback = useCallback( () => { doSomething(a, b) }, [a, b] )
The result obtained by useCallback is a function
The role of useCallback
The purpose of using useCallback is usually to optimize the function to be passed to the child component when passing the function to the child component, so as to avoid the child component rendering multiple times;
It is not for the purpose of not redefining the function, nor is it for the optimization of the function definition
Let's take a look at the following case:
Define a child component Test and pass the increment function to the child component. In the child component, we can get the increment method to modify the counter in the App component;
Since the counter changes, a new increment function will be redefined. Therefore, as long as we modify the counter, we will pass a new increment function to the Test component; the props in the Test component will change, and the Test component will be re-rendered
import React, { memo, useState, useCallback } from 'react' const Test = memo((props) => { ("Test component is rerendered") return ( <div> <button onClick={}>Test+1</button> </div> ) }) const App = memo(() => { const [counter, setCounter] = useState(10) function increment() { setCounter(counter + 1) } return ( <div> <h2>{counter}</h2> <button onClick={increment}>+1</button> <Test increment={increment}/> </div> ) }) export default App
If you define another method changeMessage in the App component at this time to modify the message;
We will find that when the message changes, the child component Test will also be re-rendered; this is because the message changes and the App component will be re-rendered. Then a new increment function will be re-define and the new increment function will be passed to the Test component. If the props of the Test component are changed, it will be re-rendered.
import React, { memo, useState, useCallback } from 'react' const Test = memo((props) => { ("Test component is rerendered") return ( <div> <button onClick={}>Test+1</button> </div> ) }) const App = memo(() => { const [counter, setCounter] = useState(10) const [message, setMessage] = useState("Ha ha ha ha") function increment() { setCounter(counter + 1) } return ( <div> <h2>{counter}</h2> <button onClick={increment}>+1</button> <h2>{message}</h2> <button onClick={() => setMessage("Hahahaha")}>Revisemessage</button> <Test increment={increment}/> </div> ) }) export default App
However, if we use useCallback, we can avoid the Test component re-rendering when the message in the App component changes.
Because the message component has changed, but our useCallback function below depends on counter. When the dependency has not changed, the value returned by multiple definitions is the same (that is, when modifying the message to re-render the App component, the increment is not redefined, but is still the previous one); this means that the props in the Test component have not changed, so the Test component will not be re-rendered
If the counter value changes, because the useCallback function depends on the counter, a new function will be defined to increment; when a new increment is passed to the Test component, the props of the Test component will change, and the Test will still be re-rendered, which is also the effect we want to achieve
import React, { memo, useState, useCallback } from 'react' const Test = memo((props) => { ("Test component is rerendered") return ( <div> <button onClick={}>Test+1</button> </div> ) }) const App = memo(() => { const [counter, setCounter] = useState(10) const [message, setMessage] = useState("Ha ha ha ha") // Use useCallback to rely on counter const increment = useCallback(() => { setCounter(counter + 1) }, [counter]) return ( <div> <h2>{counter}</h2> <button onClick={increment}>+1</button> <h2>{message}</h2> <button onClick={() => setMessage("Hahahaha")}>Revisemessage</button> <Test increment={increment}/> </div> ) }) export default App
Further optimization can be performed:
Now when our code changes, useCallback will redefine a new function and return it to increment; but we want to do that, if the counter changes, it still uses the original function, and there is no need to redefine a new function;
Some friends may think that they can change the dependency to an empty array, but if this is the case, a closure trap will be generated;
When we modify the counter, we will not regenerate a new function, but the counter used in the original function will always be the previous value, that is, 0;
This is because at the moment our old function was defined, the counter value was 0;
Since the counter is modified, the old function is still used, no matter how many times we modify the counter, the data displayed on the page will always be a result of 0 + 1
const increment = useCallback(() => { setCounter(counter + 1) }, [])
At this time, we need to use another hook: useRef
When the useRef function renders the component multiple times, the same value is returned;
We can store the latest counter into the current property of the object returned by useRef;
The advantage of this is that when the counter changes, a function will not be redefined, which means that modifying the counter will not cause the Test component to be rerendered.
import React, { memo, useState, useCallback, useRef } from 'react' const Test = memo((props) => { ("Test component is rerendered") return ( <div> <button onClick={}>Test+1</button> </div> ) }) const App = memo(() => { const [counter, setCounter] = useState(10) const [message, setMessage] = useState("Ha ha ha ha") // The component is rendered multiple times, and the same ref object is returned const counterRef = useRef() // Save the latest counter to the current property of the ref object = counter const increment = useCallback(() => { // When modifying data, refer to the latest value saved to the current property of the ref object setCounter( + 1) }, []) return ( <div> <h2>{counter}</h2> <button onClick={increment}>+1</button> <Test increment={increment}/> <h2>{message}</h2> <button onClick={() => setMessage("Hahahaha")}>Revisemessage</button> </div> ) }) export default App
UseMemo analysis
The actual purpose of useMemo is also to optimize performance, for example, the following example
We define a function that calculates the accumulation, and call this function in the App component to calculate the result
However, when the counter changes, the App component will be re-rendered, and the calcNumTotal function will be re-computed; however, the change of counter has nothing to do with the calcNumTotal function, but it needs to be re-rendered; in this similar scenario, we can use useMemo for performance optimization
import React, { memo } from 'react' import { useState } from 'react' // Define a function to sumfunction calcNumTotal(num) { let total = 0 for (let i = 1; i <= num; i++) { total += i } return total } const App = memo(() => { const [counter, setCounter] = useState(10) return ( <div> {/* couter changes, component re-renders, which means that the calcNumTotal function will also be re-executed, and the result will be re-calculated */} <h2>Calculation results: {calcNumTotal(100)}</h2> <h2>Current count: {counter}</h2> <button onClick={() => setCounter(counter + 1)}>+1</button> </div> ) }) export default App
How to use useMemo to optimize performance?
The useMemo returns a memoized (memorized) value; when the dependency remains unchanged, the returned value is the same when it is defined multiple times;
- Parameter 1: Pass a callback function
- Parameter 2: Pass in an array, indicating dependency, and pass in an empty array without dependency; if not passed, the function will do nothing, meaningless
const memoizedValue = useMemo( () => { computeExpensiveValue(a, b) }, [a, b] )
In this way, we can optimize the above code and implement the counter changes, while the calcNumTotal function does not need to recalculate the result
import React, { memo, useMemo, useState } from 'react' // Define a function to sumfunction calcNumTotal(num) { ("calcNumTotal function is called") let total = 0 for (let i = 1; i <= num; i++) { total += i } return total } const App = memo(() => { const [counter, setCounter] = useState(10) let result = useMemo(() => { return calcNumTotal(50) }, []) return ( <div> {/* couter changes, component re-renders, which means that the calcNumTotal function will also be re-executed, and the result will be re-calculated */} <h2>Calculation results: {result}</h2> <h2>Current count: {counter}</h2> <button onClick={() => setCounter(counter + 1)}>+1</button> </div> ) }) export default App
The difference between useMemo and useCallback:
The return value of the incoming callback function obtained by useMemo, the incoming callback function obtained by useCallback itself;
Simply put, useMemo is to optimize the return value of the function, and useCallback is to optimize the function;
useCallback(fn, []) and uesMemo(() => fn, []) express the same meaning
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.