React Reconciliation

Chapter 13 32 mins

Learning outcomes:

  1. What is reconciliation
  2. What is diffing
  3. How diffing works in React
  4. Difference between the reconciler and renderer

Introduction

By design, as we've seen up to this point in this course, React is not at all difficult to get started with. In fact, React is a big reason why folks might not be that much afraid of working with UI in the browser any more since it helps us abstract away a lot of the imperativeness from the DOM.

However, this is not to say that we shouldn't strive hard to understand some of the internal technicalities of React. Clearly, the best way to learn any technology is to dig deeper into it and understand its design from ground up, or at least get really close to the ground.

What do you say?

When we talk about React, yes it's really fascinating to know that it rids us off the worry of having to granularly deal with the DOM, and that it allows us to describe our entire UI at once, and then takes the charge of interfacing with the DOM almost completely by itself. But how does this all work remains a crucial question.

  • How does React know which element to mutate in the DOM and which not?
  • How does React decide which attributes to remove from a given DOM node?
  • When does React remove a DOM node and introduce a different node in place of it?
  • When does React just move a node without deleting it?

These are just some of the questions that should be addressed right at this stage before we move on to explore other rich avenues in React. This is because they are all the keys to unlocking our confidence with working in React.

In this chapter, we shall address many suchlike questions by getting to the very core concept underlying them — the idea of reconciliation.

In particular, we'll see what exactly is reconciliation, what is diffing (an integral part of reconciliation), how diffing works by considering a handful of examples, and finally what is the distinction and relation between a reconciler and a renderer.

Keep in mind that this chapter is going to be a little bit technical, so be prepared, for we are to discover some implementation details of React vital to fluidly using this spectacular UI library.

What is reconciliation?

We'll begin by defining reconciliation in React.

But before we do that, let's look up the general meaning of the term in English. Clearly, the React team must have been inspired from the meaning of the word itself before using it to represent an internal concept.

The definition that we feel really aligns with what we're about to explore shortly below is as follows, from Merriam Webster, for the word 'reconcile':

:to make consistent or congruous

Hmm. Reconcile means to 'make consistent.' Right. So then, obviously, reconciliation is the process of reconciling, i.e. making consistent.

But what exactly are we trying to make consistent here? That's what we're about to see next.

When React initially renders a tree of React elements to the DOM, it takes those elements and dumps them right away inside the given root element (provided to ReactDOM.createRoot()). So far, so good. However, when React re-renders the same tree by virtue of a state change, it can't obviously dump everything again into the root element — that would be way too inefficient.

What it has to do at this stage is to take the new tree of React elements produced by the state change,

  • compare them with the previous tree of React elements (before the state change),
  • compute the differences in both of them, and then
  • apply the minimum number of changes to the current DOM in the browser

to make that DOM consistent with the latest tree of React elements.

And there we have our notion of 'consistency'. It's time to define reconciliation.

To keep things simple, we'll define it from the perspective of the DOM; yet it applies to all the different kinds of rendering targets in React.

Reconciliation is the whole process of making the current DOM tree in the browser consistent with the latest tree of React elements produced after a re-render.

While we're at it, take note of the fact that there are multiple ways of seeing and defining reconciliation.

The official docs (legacy) don't really formally define reconciliation; they only explain it, and we can just make inferences out of that explanation.

Sometimes, reconciliation is used interchangeably with the term 'diffing' (which we shall learn later on below), however we strongly believe that diffing is a slightly different, but surely related, idea. We try not to mix up reconciliation and diffing.

Anyways, so reconciliation is the name given to the whole process where React takes the current DOM tree and works on making it consistent with the latest tree of React elements.

In a similar vein, we can define a reconciler in React.

A reconciler is simply a part of React used to perform reconciliation.

Again, the term 'reconciler' is not formally defined by the official docs, and thus we need to infer its meaning, which honestly isn't that hard.

Besides, the source code of React uses the term 'reconciler' quite often, which hints us that yes, a reconciler indeed is something of concern in React.

As we shall see in the next chapter, since React 16.0, the reconciler used by React is codenamed Fiber. There are many other features of Fiber to consider, all of which will be covered in that chapter.

Reconciliation is one of the killer ideas of React, besides components, JSX, state, and so on. It's the means by which we're able to express our UI once and then React itself improvises on it to produce a corresponding DOM tree. The whole process is fast, efficient, and powerful.

Verily, some serious thought would've been put into coming up with the idea of reconciliation. And that's essentially what programming is all about — solving computational problems.

What is diffing?

Now, let's talk about the concept of diffing, which plays an integral role in the overall reconciliation process.

Recall that reconciliation is all about comparing the current React element tree with the new one to compute which things have to be added, which have to be removed, and so on, and then applying those changes to the end medium (which is the browser's DOM in our case).

The actual comparison part is commonly referred to as diffing.

Diffing is to compare the new tree of React elements with the old one to see what's different.

Again, 'diffing' is not a formally defined term in React docs, but it's not as nascent if compared to 'reconciliation'. In the world of programming, the term 'diffing' is commonly used when expressing a similar idea of comparing two data structures for differences.

Now, it's quite interesting to see how React approaches this computational problem of diffing two trees of React elements, as we explore in the next section.

Can't rely on traditional algorithms! We need assumptions

While there are already some popular algorithms to solve the problem of diffing two trees, React can't use those for performance reasons.

"But why?" you ask. Let's see.

React is a technology tied to the environment of the browser (at least mostly). As we all know, everything in the realm of a browser needs to happen blazingly fast. Fast is just not enough! Slow, unresponsive webpages have one and only one fate — they are quitted in the matter of seconds.

If React were to use the traditional algorithms for diffing two trees of elements, yes it definitely would've been able to produce correct results, but imagine the cost of those algorithms on the performance of web apps — they would've been much slower.

And clearly, no one ever wants slow webpages, and so does React.

If React has to solve this computational task of diffing two trees, it has to come up with an extremely efficient algorithm. However, the issue is that the most efficient algorithms won't even be able to come near the performance requirement that the browser environment seeks.

Keep in mind that the end device where the browser is running isn't in our control — the device might be featuring a subpar processor — and this just increases the performance problem by huge factor.

So now what?

Well, React innovates here. It simplifies the overall problem by making some very good, yet basic, assumptions. These are discussed in the next section below.

How does diffing work?

React goes over both the trees of React elements simultaneously and compares them, starting at their root, then going down to the first child, then moving to the grandchild, and so on, then moving towards the siblings, and so on.

At each point, it compares two corresponding elements of both the trees to determine if they have any differences or not.

In this regard, two very simple assumptions are made to simplify the overall diffing:

  • If the two elements have different types (given by their type properties, as discussed in the React Elements chapter), then they are different, regardless of whether their following subtrees are identical or not.
  • If the two elements have the same key (we'll cover keys later on), they are the same.

If the two elements being compared are found to be the same by type, React has to further dig into their subtrees to perform the same diffing process for the subtrees.

For instance, it might be that two <div>s are the same but their inner <button>s have different text, and that can only be determined by analyzing the subtrees of both the <div>s.

Even if the compared elements are the same, their attributes might be different. Or maybe their styles might be different. Fortunately, the diffing process takes care of these discrepancies.

However, if the two elements are different (either by type or by key), React can shortcut here and simply throw away the entire DOM node associated with the old React element and construct a new node for the new React element.

There's absolutely no need to dig into the subtrees of both the React elements. This can potentially save quite some time.

Don't worry if you're unable to understand this. A couple of examples are all we need.

Let's start off with them.

Same element types

When two compared React elements have the same type, React reuses the underlying DOM node for the new React element; there's no need to create a new DOM node.

In addition to this, React also reuses the state data between the re-renders. This is a desirable design decision because otherwise there would've been no way to preserve state information across re-renders of the same kind of element.

But this doesn't mean that the diffing stops when two elements are the same. No. Rather, when we have the same elements, React has to continue the diffing to compare the attributes, the styles, and finally the subtrees originating from the elements.

Suppose we have the following two trees:

Two trees of elements to diff
Two trees of elements to diff.

The diffing would end up with the following conclusion:

Two trees of elements with same everything
Two trees of elements with same everything.

The root <Greeting> elements are the same and so is the case with the <h1>s. Even the text content of both the <h1>s is the same, and so there's absolutely nothing to change in the DOM for this re-render.

This is React in action, saving us from uselessly mutating the DOM. Crazily amazing, right?

Different text content

If two compared elements turn out to be of the same type in the diffing, but different in their textual content, React will keep the element node in the DOM intact and just replace corresonding text node.

This makes sense because instead of having to throw away the entire element away, React can reuse the element and just improvise on its child text node.

Consider the following two trees being compared:

Two trees of elements to diff
Two trees of elements to diff.

Let's see how the diffing goes between these two trees:

Two trees of elements; <h1> has different text content
Two trees of elements; <h1> has different text content.

Both the root element <Greeting> and its child <h1> are the same (in terms of type) in both the trees; hence, the green boxes to denote them.

However, notice the text content of the <h1>. That's different (as depicted by the yellow box around it with an asterisk next to the box). The diffing algorithm in React immediately picks up this difference and marks the corresponding text node in the previous tree to be updated.

Different attributes

Now let's consider an instance where only some attributes have changed of a given element.

Following are both the trees:

Two trees of elements to diff
Two trees of elements to diff.

After the diffing completes, here are the judgements made:

Two trees of elements; <h1> has different class
Two trees of elements; <h1> has different class.

The <Greeting> and <h1> elements are both the same, as before. However, the class attribute of the <h1> element is different. Likewise, the diffing algorithm marks the <h1> element in the old tree to be updated in the DOM.

The attributes that don't change (i.e. their values remain the same) aren't redefined. The ones that do, they are simply updated. And if any attribute is removed, it's removed from the DOM as well. A very basic approach.

Different styles

All attributes except for style are handled the same way, as we discussed above.

style, though, gets special treatment, and rightly so — as we learnt in React JSX, style is a special prop that is to meant to be an object containing all the style declarations for the underlying element.

In particular, when two elements are compared with one another, and if they both have style set, style diffing happens based on the individual style properties (for e.g. color, backgroundColor, marginLeft, and so on):

  • New style properties are added.
  • Old style properties are removed.
  • Updated style properties are simply updated.

Let's see an example.

Consider the following two trees:

Two trees of elements to diff
Two trees of elements to diff.

And now let's see what the diffing ends up with:

Two trees of elements; <h1> has a different color style
Two trees of elements; <h1> has a different color style.

As before, <Greeting> and <h1> are the exact same (in terms of type), so they get green boxes. But when we move over to the style prop of <h1>, there is one change — color: 'blue' has become color: 'pink'.

To cater to this change, the differ marks the <h1> element in the old tree to be updated in the DOM, specifically its style object's color property.

Different elements

Now, let's see the simplest case to resolve, i.e. when we have different elements.

When two compared elements have a different type, React will tear down the old DOM node associated with the old React element and bring in a new DOM node for the new React element.

By virtue of this mere assumption of React, that two different elements produce two different results, it can significantly boost performance. The question stands "How?"

When React assumes that two different elements will produce two different results (i.e. two different subtrees), it doesn't have to step into those elements to compare their subtrees. It's all one single decision — dump the old element, bring in the new one. Period.

Keep in mind that it might be that the subtrees of those two elements are the exact same, but just because their types mismatch, React says "I don't care what's contained in the new element, I am going to throw away the old element and use this new one in place of it."

Let's see an example.

Consider the following:

Two trees of elements to diff
Two trees of elements to diff.

After diffing the trees, here's what we get:

Two trees of elements; <h1> and <div> have different types
Two trees of elements; <h1> and <div> have different types.

<Greeting> is the same as before, so comparisons continue in its subtree. Here, the <div> is contrasted with the <h1> element.

Clearly, <div> is a different type of element than <h1> and so React doesn't need to dive further into its subtree for diffing (indicated in the illustration above by the light opacity given to the subtrees); it stops the diffing immediately, moving over to the next sibling of <div> if there is any (which in this case is none).

Also, the existing <h1> element in the previous tree is marked to be torn down (including its children) and replaced with a <div> in the DOM.

Same elements, different keys

Even if the elements are the same but they have different keys, the end result of the diffing process would be the same as if the elements were different. The old one would be thrown away and the new one brought in.

Actually, when we talk about keys, the diffing is not that simple. When keys are involved, React searches for those keys in a given list of children elements and then makes the final judgment as to what should be kept intact and what should be thrown away.

We'll need to know about the concept of keys before we can proceed with this case here; likewise, we defer this to the React Keys chapter.

Reconciler vs. renderer

A common misconception developers learning React might have is that the reconciler and the renderer are the same thing. Strictly speaking, that's NOT the case.

The reconciler, as we learnt above, refers to that part of React that makes the current view (the DOM, in the case of the browser) consistent with the latest React element tree produced after a re-render.

The renderer on the other hand refers to a separate package that contains all the bells and whistles to be able to integrate with the reconciler. One trivial example is React DOM (represented by the react-dom package in our code snippets).

To better understand the distinction and relation between the renderer and the reconciler, consider the following figure:

The relation between reconciler and renderer
The relation between reconciler and renderer.

The renderer is something that itself uses the reconciler, but they are two distinct things.

The reconciler doesn't operate completely independently and, as such, doesn't really have a public API to use. The reconciler relies on information provided to it by a renderer, and that's done when the renderer calls on to the reconciler, providing it with the configuration information.

As we learnt above, the reconciler takes care of the diffing of the previous and new trees of elements in React, and then ensures that the changes are rendered out to on the underlying view (DOM in our case).

By using whatever information is supplied to it by a given renderer, the reconciler eventually flushes out all of the desired changes to the end view, whatever that view is, for e.g. the DOM, or a mobile application, a blob of text, etc.

This separation of concerns has helped fuel the creation of different kinds of renderers for React, utilizing a single core reconciler.

There are obviously certain differences in the technical implementation details of how the reconciler works with different renders, but they are beyond the scope of this chapter. Plus, they don't change the overall, generalized explanation. So nonetheless, we're well off at omitting them.

It's crucial to note here that the core React package — the one that provides us with createElement(), useState(), and all other utility functions to create a React app — does NOT contain the reconciler.

Stating it again, reconciliation isn't a part of the react package.

The reconciler is exclusively meant to be used with renderers, such as React DOM, and therefore is only seen in renderer packages, such as react-dom.