React is a JavaScript library for building user interfaces developed by Facebook. It has been designed from the ground up with performance in mind. In this article I will present how the diff algorithm and rendering work in React so you can optimize your own apps.
Before we go into the implementation details it is important to get an overview of how React works.
var MyComponent = React.createClass({ render: function() { if (this.props.first) { return <div className="first"><span>A Span</span></div>; } else { return <div className="second"><p>A Paragraph</p></div>; } } });
At any point in time, you describe how you want your UI to look like. It is important to understand that the result of render is not an actual DOM node. Those are just lightweight JavaScript objects. We call them the virtual DOM.
React is going to use this representation to try to find the minimum number of steps to go from the previous render to the next. For example, if we mount <MyComponent first={true} />
, replace it with <MyComponent first={false} />
, then unmount it, here are the DOM instructions that result:
None to first
- Create node:
<div className="first"><span>A Span</span></div>
First to second
- Replace attribute:
className="first"
byclassName="second"
- Replace node:
<span>A Span</span>
by<p>A Paragraph</p>
Second to none
- Remove node:
<div className="second"><p>A Paragraph</p></div>
Finding the minimal number of modifications between two arbitrary trees is a O(n^3) problem. As you can imagine, this isn’t tractable for our use case. React uses simple and yet powerful heuristics to find a very good approximation in O(n).
React only tries to reconcile trees level by level. This drastically reduces the complexity and isn’t a big loss as it is very rare in web applications to have a component being moved to a different level in the tree. They usually only move laterally among children.
Let say that we have a component that on one iteration renders 5 components and the next inserts a new component in the middle of the list. This would be really hard with just this information to know how to do the mapping between the two lists of components.
By default, React associates the first component of the previous list with the first component of the next list, etc. You can provide a key
attribute in order to help React figure out the mapping. In practice, this is usually easy to find out a unique key among the children.
A React app is usually composed of many user defined components that eventually turns into a tree composed mainly of div
s. This additional information is being taken into account by the diff algorithm as React will match only components with the same class.
For example if a <Header>
is replaced by an <ExampleBlock>
, React will remove the header and create an example block. We don’t need to spend precious time trying to match two components that are unlikely to have any resemblance.
Attaching event listeners to DOM nodes is painfully slow and memory-consuming.
Instead, React implements a popular technique called “event delegation”. React goes even further and re-implements a W3C compliant event system. This means that Internet Explorer
8 event-handling bugs are a thing of the past and all the event names are consistent across browsers.
Let me explain how it’s implemented. A single event listener is attached to the root of the document. When an event is fired, the browser gives us the target DOM node. In order to propagate the event through the DOM hierarchy, React doesn’t iterate on the virtual DOM hierarchy.
Instead we use the fact that every React component has a unique id that encodes the hierarchy. We can use simple string manipulation to get the id of all the parents. By storing the event listeners in a hash map, we found that it performed better than attaching them to the virtual DOM. Here is an example of what happens when an event is dispatched through the virtual DOM.
clickCaptureListeners['a'](event); clickCaptureListeners['a.b'](event); clickCaptureListeners['a.b.c'](event); clickBubbleListeners['a.b.c'](event); clickBubbleListeners['a.b'](event); clickBubbleListeners['a'](event);
The browser creates a new event object for each event and each listener. This has the nice property that you can keep a reference of the event object or even modify it. However, this means doing a high number of memory allocations. React at startup allocates a pool of those objects. Whenever an event object is needed, it is reused from that pool. This dramatically reduces garbage collection.