preface

Before hooks, if a component had internal state, we created it as a class.

In React, the performance optimization points are:

  1. callsetState, which triggers re-rendering of the component, before and afterstateWhether or not the same
  2. The parent component updates, and the child component updates automatically

Based on the above two points, our usual solution is: Use immutable for comparison, call setState when unequal, check the props and state before and after in shouldComponentUpdate, and return false to prevent the update if there is no change.

After hooks came out, there is no shouldComponentUpdate life cycle in the function component, we can’t determine whether to update by checking the state before and after. UseEffect no longer distinguishes between mount Update states, which means that every call to a function component executes all of its internal logic, resulting in a significant performance penalty.

contrast

Let’s take a brief look at useMemo and useCallback call signatures:

function useMemo<T> (factory: () => T, deps: DependencyList | undefined) :T; function useCallback<T extends (. args:any[]) = >any> (callback: T, deps: DependencyList) :T;
Copy the code

UseCallback and useMemo use the same parameters as useEffect, the major difference is that useEffect is used to handle side effects, whereas the first two hooks do not.

Both useCallback and useMemo are executed when a component is first rendered and again when its dependent variable changes; And both hooks return cached values, useMemo returns cached variables, and useCallback returns cached functions

React.memo()

In the era of class components, we often used PureComponent to perform a shallow comparison of props at a time. We can also add a further level of control in shouldComponentUpdate.

In Function, React provides react. memo HOC, which is similar to PureComponent, but specifically for Function Component. Does not apply to Class Component.

However, in contrast to PureComponent, react.Memo () can be used to specify an argument, which is equivalent to shouldComponentUpdate. Usage is more convenient.

(Of course, it would be OK if you wrapped a HOC and implemented PureComponent + shouldComponentUpdate internally, which is quite common in previous projects.)

Let’s first look at how to use react.memo () :

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */
}
export default React.memo(MyComponent, areEqual);
Copy the code

In addition to Function Component, declare an areEqual method to determine the difference between the two props. If the second parameter is not passed, only shallow comparisons are performed by default

The final export component is the react.Memo () wrapper.

Example:

  • Index.js: parent component

  • Child.js: Child component

  • ChildMemo. Js: A child component wrapped with react. memo

index.js

import React, { useState, } from 'react';
import Child from './Child';
import ChildMemo from './Child-memo';

export default (props = {}) => {
    const [step, setStep] = useState(0);
    const [count, setCount] = useState(0);
    const [number, setNumber] = useState(0);

    const handleSetStep = () = > {
        setStep(step + 1);
    }

    const handleSetCount = () = > {
        setCount(count + 1);
    }

    const handleCalNumber = () = > {
        setNumber(count + step);
    }

    return (
        <div>
            <button onClick={handleSetStep}>step is : {step} </button>
            <button onClick={handleSetCount}>count is : {count} </button>
            <button onClick={handleCalNumber}>numberis : {number} </button>
            <hr />
            <Child step={step} count={count} number={number} /> <hr />
            <ChildMemo step={step} count={count} number={number} />
        </div>
    );
}
Copy the code

child.js

This child component has no logic and no wrapper, just renders the props. Number passed by the parent component

Note that the props. Step and props. Count are not used in the child component, but any changes to the props.

import React from 'react';

export default (props = {}) => {
    console.log(`--- re-render ---`);
    return (
        <div>{/ *<p>step is : {props.step}</p>* /} {/ *<p>count is : {props.count}</p>* /}<p>number is : {props.number}</p>
        </div>
    );
};
Copy the code

childMemo.js

This subcomponent is wrapped with react. memo, and the isEqual method determines that rendering will only be triggered again if the props number is twice, otherwise console.log will not execute.

import React, { memo, } from 'react';

const isEqual = (prevProps, nextProps) = > {
    if(prevProps.number ! == nextProps.number) {return false;
    }
    return true;
}

export default memo((props = {}) = > {
    console.log(`--- memo re-render ---`);
    return (
        <div>{/ *<p>step is : {props.step}</p>* /} {/ *<p>count is : {props.count}</p>* /}<p>number is : {props.number}</p>
        </div>
    );
}, isEqual);
Copy the code

Effect of contrast

As you can see from the figure above, when you click step and count, both props. Step and props. Count change, so the Child child.js is rerendering each time (—-re-render—-), Even without using these two props.

Childmemo.js does not re-render in this case.

Childmemo.js and child.js behave the same only when the props. Number changes.

As you can see from the above, the second method of react.Memo () must exist for certain requirements. Because in the experimental scenario, we can see that even though I wrapped child-.js with react. memo, it kept triggering the re-render because the props were shallower.

React.usememo () fine-grained performance optimization

The react.memo () method is used to wrap the entire component in the outer layer, and you need to manually write a method to compare the specific props before re-render.

In some cases, we just want parts of the Component not to be re-render, not the entire component not to be re-render, i.e. partial Pure.

The basic uses of useMemo() are as follows:

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

UseMemo () returns an Memoized value, which is recalculated only when dependencies (such as a and b above) change.

The render logic will not be retriggered if memoized values remain unchanged.

Speaking of render logic, keep in mind that useMemo() is executed during render, so you can’t do extra side operations like network requests, etc.

If the dependency array is not provided ([a,b] above) then the Memoized value is recalculated each time, resulting in re-redner

Add a child-usemmo.js Child component to the above code as follows:

import React, { useMemo } from 'react';

export default (props = {}) => {
    console.log(`--- component re-render ---`);
    return useMemo(() = > {
        console.log(`--- useMemo re-render ---`);
        return <div>{/ *<p>step is : {props.step}</p>* /} {/ *<p>count is : {props.count}</p>* /}<p>number is : {props.number}</p>
        </div>
    }, [props.number]);
}
Copy the code

The only difference is that useMemo() is used to wrap the logic for rendering part of the return, and the declaration relies on props. Number. Nothing else changes.

Effect comparison:

As you can see in the figure above, every step/count update of the parent component triggers the re-render of the useMemo wrapped child component, but the number does not change, indicating that the HTML re-render is not triggered

Re-render inside the useMemo() wrapper is retriggered only when the props. Number of the dependencies changes

React.useCallback()

With useMemo out of the way, useCallback is next. UseCallback is similar to useMemo, but it returns cached functions. Let’s look at the simplest use:

const fnA = useCallback(fnB, [a])
Copy the code

Example:

import React, { useState, useCallback } from 'react';
import Button from './Button';

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const handleClickButton1 = () = > {
    setCount1(count1 + 1);
  };

  const handleClickButton2 = useCallback(() = > {
    setCount2(count2 + 1);
  }, [count2]);

  return (
    <div>
      <div>
        <Button onClickButton={handleClickButton1}>Button1</Button>
      </div>
      <div>
        <Button onClickButton={handleClickButton2}>Button2</Button>
      </div>
    </div>
  );
}
Copy the code

The Button component

// Button.jsx
import React from 'react';

const Button = ({ onClickButton, children }) = > {
  return (
    <>
      <button onClick={onClickButton}>{children}</button>
      <span>{Math.random()}</span>
    </>
  );
};

export default React.memo(Button);
Copy the code

You might notice the react. memo method, which does a shallow comparison of the props, and does not re-render the component if the props are not changed.

The Button components above all need an onClickButton props. Although the components are optimized internally with React. Memo, we declare handleClickButton1 to define a method directly. The new method looks the same as the old method, but it is still two different objects. The memo was compared and found that the object props had changed, so it was rendered again.

const handleClickButton2 = useCallback(() = > {
  setCount2(count2 + 1);
}, [count2]);
Copy the code

Our method wraps a layer of code using useCallback and then passes in a variable called [count2], where useCallback decides whether to return a new function based on whether count2 has changed, and the internal scope of the function is updated accordingly.

Since our method only relies on count2 and count2 only updates handleClickButton2 after clicking Button2, clicking Button1 does not re-render Button2’s content.

conclusion

  1. In cases where the child component does not need the values and functions of the parent component, you simply wrap the child component with the Memo function.

  2. If a function is passed to a child component, use useCallback

  3. If a value is passed to a child component, use useMemo

  4. UseEffect, useMemo, and useCallback are all built-in closures. That is, every time a component is rendered, it captures the state (props) in the context of the current component function, so every time these three hooks are executed, they reflect the current state, you cannot use them to capture the previous state. In this case, we should use ref for access.