Optimize React rendering
In order to minimize the number of DOM updates, React uses a reconciliation algorithm. To optimize the rendering even further, we can use pure components which are re-rendered only if their props change. All of that seems to work well, but in practice we can encounter terrible slowdowns making the interface unusable. This is generally due to components which are needlessly re-rendered.
We will see how to measure the performance of a React application, to understand which components are re-rendered too much, and present some common pitfalls.
Measuring performance
Facebook proposes the perf add-on to print a performance evaluation in a given interval of time. Adding the following component in your page:
will show you two buttons:
With Start we start the recording of performance. With Stop we display the results:
These results are the outputs of the commands printDOM()
, printInclusive()
, printExclusive()
and printWasted()
. The most important table is the last one: it shows the time wasted by computing a component’s DOM which is identical to the previous one. In this example, the text inputs of our forms were unusable due to rendering lags. We presume that the CarCard
component is the source of the problem.
Common pitfalls
The most common source of slowdowns is the creation of fresh properties at each re-render, defeating the props comparison of pure components. See this list of common performance anti-patterns.
In our example, the mistake is to compute the car
property of <CarCard car={...} />
by creating an new object from our Redux store. Every time the store is updated, a new car
object is created and the pure component <CarCard car={car} />
is re-rendered. Indeed, to decide if a pure component should update, React compares its new properties according to the ===
equality. By creating new car objects, we create different objects for ===
, even if the values of the fields are the same.
As a solution, we could replace the shallow equality ===
with a deep equality like _.isEqual
from Lodash. However, this equality is too slow to compute at each rendering, as it is proportional to the size of the properties. A better solution is to somehow memoize the car properties, to prevent the creation of identical car objects. Some libraries such as Reselect are made exactly for that.
React itself is also a great memoization library, for both the DOM and the props. Moreover, React handles the memoization of arrays of values, thanks to the key
attribute. Instead of doing:
we add an intermediate component CarCardMemo
:
The CarCardMemo
component memoizes the result of the importCar()
function, since it re-renders only when the rawCar
object changes. Thanks to the diff algorithm of React, even when we insert new elements in the rawCars
array, older cars are not re-computed.
Finally, we measure a new performance:
meaning that we solved our rendering problem.
Comments