What does React do?
To me, React is all about mapping data to HTML code. In their most basic form, React components (views) are a function that takes data and outputs DOM elements:
Here’s an example of the render function of a React component:
What happens if the data changes? Most traditional frameworks would append another
li to the list of todo items.
React, however, doesn’t encourage you to directly modify the DOM. The UI only changes when the underlying data changes. So to update the UI you must update the data.
And then React re-renders everything!
Isn’t re-rendering everything really slow?
There are two reasons why this doesn’t negatively impact React’s performance.
Firstly, the virtual DOM. When React renders a component it doesn’t just dump the HTML into the page body. Instead, it constructs its own DOM representation internally.
Once the internal rendering process is complete, React compares the virtual DOM to what already exists on the page. It then only updates the parts of the page that have changed since the last rendering process. This process is called reconciliation.
(React doesn’t compare to the actual DOM, so it won’t notice any changes you might have made to the DOM outside of React.)
As a result, React is actually very performant.
Preventing a full re-render with shouldComponentUpdate
By default, React calls the
render method of every component on the page every time the data changes.
React’s solution to this is the
shouldComponentUpdate method. It’s a function on a React component, just like
You need to implement shouldComponentUpdate so that it returns false if the component data hasn’t changed.
For example, you could do a deep comparison between the previous data and the new data, to see if anything has changed.
The app in this video logs calls to the render method and shows how the behavior changes when using
shouldComponentUpdate is used only the components whose data has changed are re-rendered.
Using immutable data to avoid a deep comparison in shouldComponentUpdate
The problem with manually checking if the data has changed is that it’s computationally expensive and might not be much faster than re-rendering the component.
Immutable data can provide a solution to this problem. Take this code using Immutable.js as an example:
An immutable object can’t be modified. Instead calling
.set returns a new object:
And now it’s easy to check if our data has changed:
Isn’t creating a new object for every mutation expensive?
In practice, however, object mutation is unlikely to be a bottleneck. Other calculations and DOM rendering are a lot more expensive.
Immutable.js also re-uses the parts of an object that haven’t changed. This is called “structural sharing”.
This picture (taken from a great talk by Lee Byron) illustrates structural sharing. The yellow dot represents a mutation deep inside the tree.
Other benefits of structural sharing
Another benefit of structural sharing is that data that isn’t mutated retains its reference.
For example, our todo app could have this data using Immutable.JS:
We can use the
updateIn function to add another todo item. Here Immutable.JS is less pleasant to work with than native objects, where we could just call
We can now use a simple reference comparison to detect whether our data has changed:
The list of todos has also changed:
But, the first todo hasn’t changed, it’s still the same object!
This makes it easy to re-render the components where the underlying data has changed, while skipping updates where the data remains the same.
Using Immutable with React
If you’re using Immutable.js, a reference comparison in
shouldComponentUpdate is enough to determine if a component’s data has changed. You can also use the
PureRenderMixin to avoid having to compare data manually.