React. Differences among memo(), useCallback(), and useMemo()

Posted by scarabee on Tue, 28 Dec 2021 09:54:48 +0100

  1. React.memo()
  2. React.useCallback()
  3. React.useMemo()

React.memo()

In React, when the props or state of the component changes, the view will be re rendered, and the actual development will encounter unnecessary rendering scenes. Take an example:

Subcomponents:

function ChildComp () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

Parent component:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  return (
    <div>
      <button onClick={increment}>Hits:{count}</button>
      <ChildComp />
    </div>
  );
}

There is a console statement in the sub component. Whenever the sub component is rendered, a print message will be seen on the console.

Clicking the button in the parent component will modify the value of the count variable, which will cause the parent component to re render. At this time, the child component has not changed at all (props, state), but you can still see the print information of the rendered child component in the console.

(the line printed in the console is printed by the rendering sub component when the parent component is rendered for the first time. When you click the button to re render the parent component later, the sub component is not re rendered.)

 

What we expect: when the props and state of the child components do not change, do not render the child components even if the parent component is rendered.

solve

Modify the sub components with react Memo () is wrapped in one layer.

This writing method is the high-order component writing method of React, which takes the component as the parameter of the function (memo), and the return value of the function (ChildComp) is a new component.

import React, { memo } from 'react'

const ChildComp = memo(function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
})

Feel up 👆 That kind of writing is awkward. You can open it and write it.

import React, { memo } from 'react'

let ChildComp = function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

ChildComp = memo(ChildComp)

At this point, click the button again to see the information that no printing sub components are rendered on the console.

(the line printed in the console is printed by the rendering sub component when the parent component is rendered for the first time. When you click the button to re render the parent component later, the sub component is not re rendered.)

React.useCallback()

Don't think it's over here!

In the above example, the parent component simply calls the child component without passing any properties to the child component.
Take an example of a parent component passing attributes to a child component:

Subcomponents: (subcomponents are still wrapped with React.memo())

import React, { memo } from 'react'

const ChildComp = memo(function ({ name, onClick }) {
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {name}</div>
    <button onClick={() => onClick('hello')}>change name value</button>
  </>
})

Parent component:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)  // A new function is created when the parent component renders

  return (
    <div>
      <button onClick={increment}>Hits:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

The parent component passes the name attribute and onClick attribute when calling the child component. At this time, click the button of the parent component to see the rendered information of the child component printed in the console.

 

React.memo() failed???

Analyze the following reasons:

  • Click the parent component button to change the count variable value (state value of the parent component) in the parent component, resulting in re rendering of the parent component;
  • When the parent component re renders, the changeName function will be re created, that is, the onClick attribute passed to the child component changes, resulting in the rendering of the child component;

It seems that everything is in the past. Because the props of the sub components have changed, the sub components are rendered. No problem!

Looking back, we just clicked the button of the parent component without any operation on the child component. We don't want the props of the child component to change at all.

The useCallback hook further refines this flaw

solve

Modify the changeName method of the parent component and wrap it with the useCallback hook function.

import React, { useCallback } from 'react'

function ParentComp () {
  // ...
  const [ name, setName ] = useState('hi~')
  // Each time the parent component renders, the same function reference is returned
  const changeName = useCallback((newName) => setName(newName), [])  

  return (
    <div>
      <button onClick={increment}>Hits:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

At this time, click the parent component button, and the console will not print the rendered information of the child component.

The reason: useCallback() acts as a cache. Even if the parent component is rendered, the function wrapped by useCallback() will not be regenerated and will return the last function reference.

React.useMemo()


problem

What's useMemo for?

When the parent component calls the child component, the name attribute passed is a string. What happens if it is passed as an object?

In the following example, the parent component passes the info attribute when calling the child component. The value of info is an object literal. When you click the parent component button, you will find that the console prints the rendered information of the child component.

import React, { useCallback } from 'react'

function ParentComp () {
  // ...
  const [ name, setName ] = useState('hi~')
  const [ age, setAge ] = useState(20)
  const changeName = useCallback((newName) => setName(newName), [])
  const info = { name, age }    // Complex data type properties

  return (
    <div>
      <button onClick={increment}>Hits:{count}</button>
      <ChildComp info={info} onClick={changeName}/>
    </div>
  );
}

The analysis reason is the same as calling the function:

  • Click the parent component button to trigger the parent component to re render;
  • When the parent component is rendered, the line const info = {name, age} will regenerate a new object, resulting in the change of the info attribute value passed to the child component, resulting in the re rendering of the child component.

solve

Use useMemo to wrap a layer of object properties.

useMemo has two parameters:

  • The first parameter is a function. The returned object points to the same reference and will not create a new object;
  • The second parameter is an array. The function of the first parameter will return a new object only when the variables in the array change.
function ParentComp () {
  // ....
  const [ name, setName ] = useState('hi~')
  const [ age, setAge ] = useState(20)
  const changeName = useCallback((newName) => setName(newName), [])
  const info = useMemo(() => ({ name, age }), [name, age])   // Wrap a layer

  return (
    <div>
      <button onClick={increment}>Hits:{count}</button>
      <ChildComp info={info} onClick={changeName}/>
    </div>
  );
}

Click the parent component button again, and the rendered information of child components will no longer be printed in the console.


Original text: https://www.jianshu.com/p/014ee0ebe959

Topics: Javascript Front-end React