React Hooks (5) useCallback and useMemo

A React.memo and useMemo

1. The role of memo

When the data of the parent component changes, the code will be re-executed, and the sub-component data will be executed if there is no change. At this time, you can use memo to encapsulate the sub-component, so that the sub-component data will only be executed when it changes.

Let's first look at an example without memo:
changing the value of count and num will trigger the re-rendering of the child component

import React, {
    
     useEffect, useCallback, useState, useMemo } from 'react';
import Test4 from './Test4'

const Test2 = () => {
    
    
  const [count, setCount] = useState(0)
  const [num, setNum] = useState(0)
  
  const changeCount = () => {
    
    
    setCount(count=>count+1)
  }

  const changeNum = () => {
    
    
    setNum(num=>num+1)
  }
  
  return (
   <>
   <div onClick={
    
    changeCount}>
    {
    
    count}
   </div>
   <div onClick={
    
    changeNum}>
     {
    
    num}
   </div>
   <Test4 num={
    
    num} />
   </>
  );

}

export default Test2;
import React, {
    
     useEffect, useRef, useState } from 'react';

const Test4 = (props) => {
    
    

    const {
    
    num} = props
    console.log('children-----')

    return (
     <div>
      {
    
    num}
     </div>
    );
  }
  
export default Test4;

If we wrap the subcomponent with memo, we can control the subcomponent to render only when num changes.

export default React.memo(Test4);

But there is a bug here, if the function is customized on the sub-component, then changing the value of count can still make the sub-component re-render.

import React, {
    
     useEffect, useCallback, useState, useMemo } from 'react';
import Test4 from './Test4'

const Test2 = () => {
    
    
  const [count, setCount] = useState(0)
  const [num, setNum] = useState(0)
  
  const changeCount = () => {
    
    
    setCount(count=>count+1)
  }

  const changeNum = () => {
    
    
    setNum(num=>num+1)
  }

  const clickNum = () => {
    
    
    console.log('click-----')
  }

  return (
   <>
   <div onClick={
    
    changeCount}>
    {
    
    count}
   </div>
   <div onClick={
    
    changeNum}>
     {
    
    num}
   </div>
   <Test4 num={
    
    num} clickNum={
    
    clickNum} />
   </>
  );

}

export default Test2;
import React, {
    
     useEffect, useRef, useState } from 'react';

const Test4 = (props) => {
    
    

    const {
    
    num, clickNum} = props
    console.log('children-----')

    return (
     <div onClick={
    
    clickNum}>
      {
    
    num}
     </div>
    );
  }
  
export default React.memo(Test4);

The main reason is that when the count value changes, the code is re-executed, and the address of the clickNum empty function will change, causing the subcomponent to re-render.
Summary:
The role of memo is equivalent to the PureComponent of function components.
PureComponent will implement shouldComponentUpate() based on the shallow comparison of props and state. However, if a more complex data structure is included in PureComponent, a wrong negative judgment may be generated due to deep data inconsistency, resulting in an interface that cannot be updated.
The same is true for memo. If the function component is wrapped by React.memo and has a useState or useContext Hook in its implementation, it will still re-render when the context changes. And memo is a shallow comparison, as long as the memory address of the object remains unchanged, no matter how the value changes, render will not be triggered.

2. The role of useMemo

(1) To solve the problem of rendering itself due to function update, you can use useMemo to repackage the function.
useMemo(fn,array)Monitor variables, the first parameter is the function, the second parameter is the dependency, the function will be recalculated only when the dependency changes.
In the above example, we can modify the clickNum function as follows. Only when num changes will the execution of the function be triggered, and the re-render of the subcomponent will be triggered.

const clickNum = useMemo(() => {
    
    
    return ()=>{
    
    
      console.log('num changed')
    }
},[num])

It can also be replaced with useCallback here, the effect is the same:

const clickNum = useCallback(() => {
    
    
    console.log('num changed')
  },[num])

In this way, changing the value of count will not trigger the update of the child component. In fact, after the function is wrapped by useMemo or useCallback, it will decide whether to return a new function
according to the subsequent dependencies , and the internal scope of the function will also be updated accordingly. (2) If there are complex calculation functions in the component, useMemo should be used. Because useCallback caches references to functions, useMemo caches values ​​for computed data. useMemo is an optimization strategy to avoid expensive calculations every time a render is performed.

import React, {
    
     useEffect, useCallback, useState, useMemo } from 'react';
import Test4 from './Test4'

const Test2 = () => {
    
    
  const [count, setCount] = useState(0)
  const [num, setNum] = useState(0)
  
  const changeCount = () => {
    
    
    setCount(count=>count+1)
  }

  const changeNum = () => {
    
    
    setNum(num=>num+1)
  }

  const doubleNum = useMemo(() => {
    
    
    return 2*num
  },[num])

  return (
   <>
   <div onClick={
    
    changeCount}>
    {
    
    count}
   </div>
   <div onClick={
    
    changeNum}>
     {
    
    num}
   </div>
   <div>{
    
    doubleNum}</div>
   </>
  );

}

export default Test2;

If doubleNum is a function with a complex calculation process or high overhead, you can use useMemo to obtain the cached value to avoid multiple executions of the function.

二 useCallback

Both useMemo and useCallback are executed when the component is rendered for the first time, and then executed again when the variables it depends on change; and both hooks return the cached value, useMemo returns the cached variable, and useCallback returns the cached function.
Official equation: useCallback(fn,deps) 相当于 useMemo(()=>fn,deps)。
We can combine useCallback with useEffect to achieve on-demand loading. Through the cooperation of hooks, the function is no longer just a method, but can participate in the data flow of the application as a value.

const Parent = () => {
    
    
  const [ count, setCount ] = useState(0);
  const [ step, setStep ] = useState(0);

  const fetchData = useCallback(() => {
    
    
    const url = "https://count=" + count + "&step=" + step;
  }, [count, step])

  return (
    <Child fetchData={
    
    fetchData} />
  )
}

const Child = (props) => {
    
    
  useEffect(() => {
    
    
    props.fetchData()
  }, [props.fetchData])

  return (
    // ...
  )
}

Let's look at the problems caused by frequent updates of useContext

const [text, setText] = useState('');

const handleSubmit = useCallback(() => {
    
    
  // ...
}, [text]);

return (
  <form>
    <input value={
    
    text} onChange={
    
    (e) => setText(e.target.value)} />
    <OtherForm onSubmit={
    
    handleSubmit} />
  </form>
);

Frequent updating of text will lead to frequent execution of handleSubmit, which consumes a lot of performance. Here we can optimize with useRef.

const textRef = useRef('');
const [text, setText] = useState('');

const handleSubmit = useCallback(() => {
    
    
  console.log(textRef.current);
  // ...
}, [textRef]);

return (
  <form>
    <input value={
    
    text} onChange={
    
    (e) => {
    
    
      const {
    
     value } = e.target;
      setText(value)
      textRef.current = value;
    }} />
    <OtherForm onSubmit={
    
    handleSubmit} />
  </form>
);

The dependency of useCallback only compares the value, if it is an object, it only compares the reference. And textRef is a cross-lifecycle object that always exists and will not be destroyed, and the reference remains unchanged, which is equivalent to the dependency of useCallback on []. Using useRef can generate a variable that can be accessed in each life cycle of the component, and handleSubmit will not be updated because of the update of the text, so that OtherForm will not be rendered multiple times.

Three about useMemo and useCallback

1. Common point:
Only when the data changes, the result will be recalculated, which is to play the role of cache.
2. Differences:
(1) The calculation result of useMemo is the value returned by return, which is mainly used to cache the value of the calculation result, and the application scenario is: the state that needs to be calculated (2) The calculation result of useCallback
is a function, mainly used for caching functions, application scenarios Such as: functions that need to be cached, because every time any state change of a functional component will cause the entire component to be refreshed, some functions do not need to be refreshed, so they should be cached at this time to improve performance and reduce resources waste.

Guess you like

Origin blog.csdn.net/LittleMoon_lyy/article/details/124542221
Recommended