The React core development team has been working on improving React rendering speed. React 16 introduces the React. Memo (16.6.0), React. UseCallback, and React. UseMemo (React Hooks 16.8.0).

React.memo

React.memo a high-priced component used to avoid duplicate rendering of invalid components. PureComponent and shouldComponentUpdate() function similarly. But react. memo can only be used with function components, and if react. memo takes the second argument compare, compare returns true and does not render, false. This is the opposite of the React.PureComponent and shouldComponentUpdate() methods. Online instance

import * as React from 'react';
import { Button, Typography } from 'antd';

const ChildComponent = () = > {
  console.log('ChildComponent ChildComponent');
  return (
    <Button type="primary" ghost>Subcomponents ChildComponent</Button>
  );
};

const ParentComponent = () = > {
  const [count, setCount] = React.useState < number > 0;
  return (
    <div>
      <Button
        type="primary"
        style={{ marginBottom: 20 }}
        onClick={()= >{ setCount(count + 1); }} > + 1</Button>
      <Typography.Paragraph type="danger" strong>
        count:{count}
      </Typography.Paragraph>
      <ChildComponent />
    </div>
  );
};

export default ParentComponent;
Copy the code

Run. Each click of the [+1 button] causes the ChildComponent to re-render:

React rendering mechanism. Updates to ParentComponent will re-render ChildComponent. If you want to use ChildComponent without rendering, you can optimize this by using the React. Memo advanced component: online instance

const ChildComponent = React.memo(() = > {
  console.log('ChildComponent ChildComponent');
  return (
    <Button type="primary" ghost>Subcomponents ChildComponent</Button>
  );
});
Copy the code

Use React. Memo to wrap ChildComponent:

As is clear from the example, an update to the parent ParentComponent does not cause the ChildComponent to re-render after the ChildComponent is wrapped in React. Memo.

If you want more precise control over when the React.memo package is updated, you can pass the react. Memo second argument compare. Because react. memo by default only compares shallowEqual to props.

function MyComponent(props) {
  /* props render */
}
function compare(prevProps, nextProps) {
  /* Compare prevProps to nextProps */
  // True means that the component does not need to be re-rendered, false means that the component needs to be re-rendered
}
export default React.memo(MyComponent, compare);
Copy the code

Singsong: If the react. memo wrapped function component uses useState and useContext, the function component will also be updated when the context and state change.

React.memoKey source

import { REACT_MEMO_TYPE } from 'shared/ReactSymbols';

export function memo<Props> (type: React$ElementType, compare? : (oldProps: Props, newProps: Props) => boolean) {
  const elementType = {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };

  return elementType;
}
Copy the code

When to useReact.memo

  • Pure functional components (i.epropsRender output unchanged)
  • Repeat rendering frequently
  • The incomingpropsEssentially unchanged
  • Complex components (expensive to render)

React.useCallback

Before introducing useCallback, take a look at the output of the following example:

Examples of source

function sumFactory() {
  return (a, b) = > a + b;
}

const sum1 = sumFactory();
const sum2 = sumFactory();

console.log(sum1 === sum2); // => false
console.log(sum1 === sum1); // => true
console.log(sum2 === sum2); // => true
Copy the code

The sumFactory factory method returns two methods: sum1 and sum2. Although returned by the same factory method, the two are completely different.

Then look at the following example: an online instance

import * as React from 'react';
import { Button, Typography } from 'antd'; type ChildComponentType = { onChildClickCb? :() = > void};const ChildComponent: React.FC<ChildComponentType> = React.memo((props: ChildComponentType) = > {
  console.log('ChildComponent ChildComponent');
  return (
    <Button type="primary" ghost>Subcomponents ChildComponent</Button>
  );
});

const ParentComponent = () = > {
  const [count, setCount] = React.useState < number > 0;
  return (
    <div>
      <Button
        type="primary"
        style={{ marginBottom: 20 }}
        onClick={()= >{ setCount(count + 1); }} > + 1</Button>
      <Typography.Paragraph type="danger" strong>
        count:{count}
      </Typography.Paragraph>
      <ChildComponent onChildClickCb={()= >{}} / ></div>
  );
};

export default ParentComponent;
Copy the code

Run. Each click of the [+1 button] causes the ChildComponent to re-render:

There might be a question here, right? 🤣, why ChildComponent is wrapped in React. Memo. ParentComponent updates cause ChildComponent updates.

The problem is that the

{}} /> statement passes a new onChildClickCb value every ParentComponent update. For example:

= = = {} {}// false
Copy the code

Here, if you want the onChildClickCb value passed in unchanged, you can wrap useCallback.

Online instance

import * as React from 'react';
import { Button, Typography } from 'antd'; type ChildComponentType = { onChildClickCb? :() = > void};const ChildComponent: React.FC<ChildComponentType> = React.memo((props: ChildComponentType) = > {
  console.log('ChildComponent ChildComponent');
  return (
    <Button type="primary" ghost>Subcomponents ChildComponent</Button>
  );
});

const ParentComponent = () = > {
  const [count, setCount] = React.useState < number > 0;
  const onChildClickCb = React.useCallback(() = >{} []);// Wrap onChildClickCb with useCallback
  return (
    <div>
      <Button
        type="primary"
        style={{ marginBottom: 20 }}
        onClick={()= >{ setCount(count + 1); }} > + 1</Button>
      <Typography.Paragraph type="danger" strong>
        count:{count}
      </Typography.Paragraph>
      <ChildComponent onChildClickCb={onChildClickCb} />
    </div>
  );
};

export default ParentComponent;
Copy the code

React.useMemo

React. UseMemo is similar to the React. UseCallback function signature. The only difference is that react.usememo caches the return value of the first argument (nextCreate()), whereas react.usecallback caches the function of the first argument (callback). So React. UseMemo is often used to cache the return values of computationally intensive functions.

Key source

export function useCallback<T> (callback: T, inputs: Array<mixed> | void | null) :T {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  workInProgressHook = createWorkInProgressHook();

  constnextInputs = inputs ! = =undefined&& inputs ! = =null ? inputs : [callback];

  const prevState = workInProgressHook.memoizedState;
  if(prevState ! = =null) {
    const prevInputs = prevState[1];
    if (areHookInputsEqual(nextInputs, prevInputs)) {
      return prevState[0];
    }
  }
  workInProgressHook.memoizedState = [callback, nextInputs];
  return callback;
}

export function useMemo<T> (nextCreate: () => T, inputs: Array<mixed> | void | null) :T {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
  workInProgressHook = createWorkInProgressHook();

  constnextInputs = inputs ! = =undefined&& inputs ! = =null ? inputs : [nextCreate];

  const prevState = workInProgressHook.memoizedState;
  if(prevState ! = =null) {
    const prevInputs = prevState[1];
    if (areHookInputsEqual(nextInputs, prevInputs)) {
      return prevState[0]; }}const nextValue = nextCreate(); / / calculated value
  workInProgressHook.memoizedState = [nextValue, nextInputs];
  return nextValue;
}
Copy the code

WorkInProgressHook object

  {
    memoizedState: null.baseState: null.queue: null.baseUpdate: null.next: null};Copy the code

areHookInputsEqual()The source code

export default function areHookInputsEqual(arr1: any[], arr2: any[]) {
  for (let i = 0; i < arr1.length; i++) {
    const val1 = arr1[i];
    const val2 = arr2[i];
    if( (val1 === val2 && (val1 ! = =0 || 1 / val1 === 1/ (val2: any))) || (val1 ! == val1 && val2 ! == val2)// eslint-disable-line no-self-compare
    ) {
      continue;
    }
    return false;
  }
  return true;
}
Copy the code

It is clear from the source code that only the last record is recorded. Comparison algorithms are also shallow comparison.

To learn how React. UseMemo works, let’s put it to the test: an online instance

import * as React from 'react';
import { Button, Typography } from 'antd'; type ChildComponentType = { resultComputed? : number[], };const ChildComponent: React.FC<ChildComponentType> = React.memo((props: ChildComponentType) = > {
  console.log('ChildComponent ChildComponent', props.resultComputed);
  return (
    <Button type="primary" ghost>Subcomponents ChildComponent</Button>
  );
});

const ParentComponent = () = > {
  const [count, setCount] = React.useState < number > 0;
  const calculator = (num? : number) = > {
    return [];
  };
  const resultComputed = calculator();

  return (
    <div>
      <Button
        type="primary"
        style={{ marginBottom: 20 }}
        onClick={()= >{ setCount(count + 1); }} > + 1</Button>
      <Typography.Paragraph type="danger" strong>
        count:{count}
      </Typography.Paragraph>
      <ChildComponent resultComputed={resultComputed} />
    </div>
  );
};

export default ParentComponent;
Copy the code

Run. Each click of the [+1 button] causes the ChildComponent to re-render:

The problem is the following code:

const calculator = (num? : number) = > {
  return [];
};
const resultComputed = calculator();
Copy the code

Each call to Calculator () returns a new resultComputed. If you want to fix this, you can wrap the Calculator method here using react. useMemo. Online instance

  const resultComputed = React.useMemo<number[]>(calculator, []);
Copy the code

Run, click [+1 button] every time

conclusion

This article is the author’s recent learning experience to share with you. But don’t “force it because it exists.” This is only a good place to start if the page freezes, or if performance is poor. Originally React re-rendering had a negligible impact on performance. Since Memoized is still a memory drain, if you use a lot of these optimizations incorrectly, it can backfire 😀.