How to greatly improve your React app Performance – Noam Elboim/from medium

The purpose of this article is to summarize common performance defects and how to avoid them.

Sample source code for this articlePlease download it from Github)


Performance issues are nothing new in Web application development.

We all have that moment when you put a new Component into your app and suddenly every user interaction you try is lagging behind the desired effect. Sometimes, you can reuse many of the same components, and the awkward dynamic lag becomes even more pronounced. Something like this:





At that moment you probably have a few nicknames in mind for the person who wrote this component. But the best thing to do is: Do something, yes you can!

We’ll focus on the following common React performance issues:

Error shouldComponentUpdate implementation, why PureComponent didn’t save you

2. Change the DOM too quickly.

Abuse events and callbacks.

For each of these problems, we explain the root of the problem, and then we suggest some easy-to-use ways to avoid it.

Control your shouldComponentUpdate

The component hook function shouldComponentUpdate is supposed to prevent unnecessary render. ShouldComponentUpdate takes props and state as parameters to be updated. If true is returned, the render function executes, otherwise render is not executed.

The default implementation shouldComponentUpdate is to return true.

More render means more time. So we need to prevent unnecessary updates to reduce the extra time.

For this reason, you might think we should be more careful when implementing shouldComponentUpdate.

The problem

Let’s look at a simple example of using shouldComponentUpdate:





Simple shallow implementation: ‘this.props.children ! == nextProps.children’, but it’s always returning true

code

Wait, why didn’t it work?

It doesn’t work because React creates a new ReactElement every time it renders!

This means that in the shouldComponentUpdate function the Shallow Comparision is as follows: return this.props. Children! == nextProps.children; Almost equivalent to return true;

In my experience, most components usually support the ReactElement props (proptypes.node or proptyps.elemtn) in some way, such as children.

So what about PureComonent?

React.PureComponent is another way to React.Component. It does not always return true in its shouldComponent implementation, but rather is a superficial comparison of props and state.

Using PureComponent returns the same result as follows:





PureComponent component is still always returning true


Is this a bug with the PureComponent feature? I’m not sure. What we need to know is that PureComponent doesn’t work most of the time and doesn’t prevent unnecessary updates.

Possible solutions

The first thing that comes to mind is — do a deep comparison! This works, but it has two important drawbacks:

1. Running depth comparison itself is a long, heavy and time-consuming operation. Therefore, the render function cannot run until the shouldComponent function is finished. Instead of improving performance, performance will get worse.

2. This is only based on the current React Elements implementation and may be removed in a future release.

To sum up, in my opinion, using depth comparison is not a good solution.

To find a better solution, I looked at some other virtual DOM libraries to see how they solve this problem.

I found an interesting comment by Vue author Evan You about adding the React shouldeComponent feature request to vue.js. He explained that this problem could not be solved by using the “Diffing” virtual DOM because it had many unknown problems. Relying on React Elements to detect state changes in components is not a viable solution.

In practice, the React Elements comparison should not be returned in a shouldComponentUpdate implementation. Instead, you should use some kind of state change to tell the component whether it should be updated.

We should notify state changes based on prop differences, not by using this.props. Children! = = nextProps. Children. Preferably a number or string to make the comparison faster.

We can even use a new prop specifically to inform components if they should be updated.

Further, my colleagues and I created a high-level component (HOC). This component uses Inheritance Inversion to extend the common shouldComponent implementation and is an alternative to PureComponent. And it works. Here’s the code:

Github.com/NoamELB/sho…

It’s important to note that this is a generic implementation, so it’s not for every situation. For details, please refer to here

The example here uses a custom shouldComponentUpdate implementation. As mentioned above, it really doesn’t render unnecessarily anymore.





Several comparisons:





See here for example code.

Allow your components to expand

Do you use the same component multiple times in your application, causing your application to be very heavy and slow in animation, and sometimes even using one can lead to a loss in application performance?

The problem

When creating complex components, you may need to perform some custom DOM operations. You may encounter two problems when creating:

1. Triggering too many layouts without using Composite or Paint

2. Too many unnecessary layouts. Read and write DOM multiple times, resulting in unnecessary DOM recalculation.

Let’s take a look at the native Collapse component and change its height between 0 and the content height. Click to view





When one of these components is used, it can be displayed normally. But when you use it many times……





Click to see the specific effect

If you’re not viewing it on a mobile device, it might not be obvious. You need to set your Chrome Performance option to 6x Slowdowns





Possible solutions

Let’s analyze what happens to the Collapse component — here’s the code for height changes:





There are two things to note here:

1. We changed the height property. According to csstriggers.com, changing height triggers a Layout recalculation. If we tried to change something like transform, that would just trigger Composite, and it would be smoother, right?

That’s exactly what happened, it would have been better, but it would have left a gap under the Collapse component because we hadn’t changed its height.

2. The third line of the code above is a common misuse of changing the height of the start Layout: We read the height from the DOM this. ContentEl. ScrollHeight then by enclosing containerEl. Style. The height of the DOM set the height, and then repeat the operation.

Wouldn’t it be nice if we could read the height all at once and then set the height all at once?

Batch reading and writing to the DOM is a good way to reduce Layout. We can batch DOM reads and writes using requestAnimationFrame as follows:





RequestAnimationFrame ensures that your code fires on the next frame in the browser, reducing the cost of page drawing and batch drawing on demand. Make your animations smoother. Click to see the implementation

This can be tricky, but you can use built-in components or a third-party library such as Fastdom. Fastdom is also based on the principle of requesAnimationFrame to eliminate frequent Layout operations by batchingDOM reads/writes.

It’s worth noting that sometimes you may not get good enough performance due to browser and device capabilities. In these cases, the best solution may be to change the product requirements.

Finally, you’ve probably heard of the will-change property of CSS. It can help you in certain situations, but there are risks to using it poorly. It’s best not to overuse it.

Control your callbacks

When we call any DOM event, it is necessary to have a debounce or throttle function. It allows us to reduce the number of calls to this function to the minimum number of times we want to improve performance.

It is usually written like this: window.addeventListener (‘ resize ‘, _.throttle(callback)), but why can’t we apply this to React Components callbacks as well?

The problem

Let’s look at the following component:





Notice that we call this.props. OnChange every time we make an input change, and it gets called multiple times, even though many of the calls are unnecessary. If the parent is making DOM changes or any other heavy operations based on the onChange callback, our application will become sluggish.

Possible solutions

In fact, we can improve it like this:





Debounce the event

Now, props. OnChange is called only after user input is complete, which prevents a lot of unnecessary event manipulation.

A similar solution is throtle. Click to see the difference between Throttle and debounce.

conclusion

These tools should help you deal with some of the performance issues we encountered in the React application. By judiciously using shouldComponentUpdate, controlling the changes you make to the DOM, and deferring callbacks by debounce/throttle, you can greatly improve the performance of your application.

If you want to test your development, check out UiZoo. React is a dynamic component library for the React component that parses your components and shows them to you so you can develop, test, or share them with others.

Sample source code for this articlePlease download it from Github)