React optimization direction

  1. Reduce the number of rerenders. Since the diff algorithm determines that the component is in the Updating state, ifshouldComponentUpdate()returnfalse, directly stop diff, so render is not executed, reducing performance overhead
  2. Reduce the amount of computation. Mainly to reduce double computation, for functional components, each render will execute the function call from scratch again.

React.memo

First of all, if the parent component is re-rendered, then the child component is re-rendered, regardless of whether the props are changed or not. This is a feature of the Diff algorithm, hence shouldComponentUpdate, which lets the user manually tune it

ShouldComponentUpdate should mention the PureComponent added in React 15.3, which works like this: If the props and state of the component are not changed when the component is updated, the Render method does not fire to improve performance. React automatically provides a shallow comparison:

if (this._compositeType === CompositeTypes.PureClass) { shouldUpdate = ! shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState); }Copy the code

And what does shallowEqual do? Will be Object. Keys (the state | props) whether the length of the agreement, each key for both, and whether it is a reference, is the only compares the value of the first layer is very shallow, so deeply nested data as compared to not to come out. So state and props cannot be a reference type, or always return true.

The react. memo method works the same as PureComponent, except that the former is used for functional components and the latter for class components, and the second argument can be passed to a comparison function that references the comparison of type props as follows:

function MyComponent(props) {
  /* props render */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
exportdefault React.memo(MyComponent, areEqual);
Copy the code

useCallback

So what if props is a function, does the parent render, and the child render? The answer is yes. Because each time the function component is re-rendered in the functional component, the callback function must have changed, causing the child component to re-render. You might want to optimize with the react. memo above, but how do you know if the two functions are equal? Are you in a blind spot? The react. memo reference type can only be Array or Object. Function cannot be compared, so useCallback is required.

const callback = () = > {
  doSomething(a, b);
}

const memoizedCallback = useCallback(callback, [a, b])
Copy the code

Passing the function and its dependencies as arguments to useCallback will return the Memoizedversion of the callback function, which will be updated only if the dependency changes. This brings us back to comparing primitive or reference types (other than function).

useMemo

Let’s say I have a component

function App() {
  const [num, setNum] = useState(0);

  // a very time-consuming calculation function
  // result finally returns the value 49995000
  function expensiveFn() {
    let result = 0;

    for (let i = 0; i < 10000; i++) {
      result += i;
    }

    console.log(result) / / 49995000
    return result;
  }

  const base = expensiveFn();

  return (
    <div className="App">
      <h1>Count: {num}</h1>
      <button onClick={()= > setNum(num + base)}>+1</button>
    </div>
  );
}
Copy the code

This example is simple, click the +1 button, then add the current value (num) to the value after the expensiveFn call, then set the value to num and display it, on the console output 49995000.

Performance issues: Is it possible to block the page rendering every time the render function executes this expensive calculation logic? Here comes useMemo:

function computeExpensiveValue() {
  // Computationally heavy code
  return xxx
}

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

The first argument to useMemo is a function whose value is cached as useMemo’s return value. The second argument is an array dependency. If the value in the array changes, the function in the first argument is executed again. The value returned by the function is cached and used as the return value of useMemo. For the example above, the usage is

const base = useMemo(expensiveFn, []);
Copy the code

So no matter how many times we click +1, we’ll print 49995000 once, which means expensiveFn executed only once and did what we wanted.

conclusion

Performance bottlenecks may be less encountered for small projects, because of the small amount of computation and uncomplicated business logic. However, performance bottlenecks may be encountered for large projects, but there are many aspects to performance optimization: Network, critical path rendering, packaging, images, caching, etc., need to be optimized by yourself. This article only introduces the tip of the iceberg in performance optimization: React optimization during operation.

  1. React: Reduce the number of render times; Reduce double counting.
  2. See the useCallback section to find out how React causes performance problems.
  3. If you think about it this way, if you have only one large component in the entire page, then when the props or state is changed, it is the entire component that needs to reconction. In fact, you just change a text. If you make a reasonable component separation, You can control more granular updates.

There are many other benefits to splitting components properly, such as better maintenance, and this is the first step in learning about componentalization. Splitting components properly is an art form, and if not, it can lead to state chaos and more coding and more thinking.