Over the past little bit I’ve had troubles with optimization in React. I’ve begun to notice that the more complex my apps got, the longer it would take them to render and re-render. Even React can’t save me from eventual performance slowdown (mostly due to my own stupidity)…
I watched a great talk this morning given by my boss Ev, who is one without a doubt one of the smartest developers I know. In this talk he touches on things that tend to slow down our React applications, and it boils down to two main culprits:
1. Rendering to the DOM is insanely slow. Avoid it as much as you can.2. Re-rendering to the DOM is just as slow. Avoid it as much as you can.
Okay, so clearly there’s a direct message here; avoid unnecessary writes to the DOM whenever possible. Let’s take a look at this in the context of React.js.
There is really no escaping the initial
render process (otherwise users would not see anything on your page!). Knowing this, our best bet is to reduce the amount of re-renders to as little as we possibly can. This is the perfect job for
As a part of React’s lifecycle,
shouldComponentUpdate is a method that returns a boolean, which determines if the component needs to update or not.
shouldComponentUpdate by itself already seems really useful for determining if a component should re-render. Simply put, if the props/state don’t change, then don’t re-render. Voila!
But wait… This is can get really ugly really fast. I can think of two general cases where your
shouldComponentUpdate can get out of hand.
In this example, it’s extremely easy to remove or add a prop/state to the component and forget to do the same in
In this example, we have less props but more deeply nested props (which is good). However, we can no longer use a simple
=== equator, we have to compare the two objects to see if they’re different. What’s even worse is that because both are nested, we have to recursively do a deep comparison.
So why does this suck? Well because deep comparisons are expensive to perform, and when done on every prop/state of every component over an entire app, could leave a nasty scar.
shouldComponentUpdate is actually so useful, React has an add-on helper mixin called
PureRenderMixin that out-of-the-box implements it for you. All you need to do is decorate or mixin your component with the functionality. Unfortunately
PureRenderMixin also only performs shallow comparisons; which still doesn’t solve our problem.
So then what do I do?
Of course this is not actually how
Immutable.js stores your data, but the structure and the logic is the same. It is nothing more an object wrapper that make sure you can’t mutate the contained data, thus achieving the effect of immutability.
What does this have to do with optimizing React?
Here it comes. Because
Immutable.js records are immutable, everytime a change is made, a new copy of the record is returned, and the previous one is never altered. But the best part about that is that every new record also comes with a new
If all changes to
Immutable.js records return a new record with a new
reference id, that effectively means not matter how deeply nested the change, we will still be aware that the new record has INFACT changed. We can deep compare Immutable.js datatypes by simply checking if the references are different.
I’ll say it again: We can deep compare Immutable.js datatypes by simply checking if the references are different.
Didn’t our whole problem with
PureRenderMixin have to do with the inpracticality of performing deep comparisons on our props/state?
Immutable.js creates new records with new references upon any change to the data, we can do deep comparisons of current props/state to next props/state for next to nothing! In fact, so long as you use
Immutable.js for your datatypes,
PureRenderMixin will work and “deep compare” out-of-the-box without you having to do any extra configurations!
The ability to use
PureRenderMixin on any component in our app regardless of the props/state structure complexity is amazing. This allows us to make certain that components throughout our app are only ever re-rendering when they truely need to.