In-depth Diffing

In the update stage of a component, React has to determine which child components to mount, update, and unmount.

One naive approach would be to render components from scratch every time and swap out the DOM. However, components would lose their state and performance would suffer from recreating the same DOM every time. Instead, React keeps the output of the previous render call and compares it with the new output to decide what to do. This comparison is called reconciliation.

It’s important to understand how React’s reconciliation works to build performant apps as well as avoid confusing bugs. Understanding the reconciliation process will also give you more confidence to bypass React and work with the underlying DOM. Fortunately, React’s reconciliation process is simple (for good reasons) and easy to understand.

In this article, we will take a more practical approach to understanding reconciliation. It is also worth reading the official reconciliation docs to understand the design motivations.

The process

The heart of the reconciler lives in ReactChildReconciler.updateChildren. Let's walk through the code. Follow the source here.

  • React iterates through the new set of children (nextChildren).
  • For each child, React checks whether there is an old child (prevChildren) that has the same key as the new child. If an explicit key is not provided, React uses its position. [getComponentKey source].
    • If there is no new child with the same key as an old child, the old child is unmounted.
    • If there is no old child with the same key as a new child, the new child is mounted.
    • If there is an old and new child with the same key, we use shouldUpdateReactComponent to decide whether we should update the instance vs doing a clean unmount/mount. [source]

If React decides to update a child instance, React will then call render on the instance and again reconcile the output and its children. In the other case where an instance is unmounted and a new one is mounted, there is no further reconciling or DOM updates.

Understanding reconciliation in practice

Let's look at how reconciliation can drastically change the behavior and performance of a React application. Take a look at these two implementations of render.

render1() {
  if (this.state.showWarning) {
    return (
      <div>
        <Warning />
        <StatefulComponent />
      </div>
    );
  }

  return (
    <div>
      <StatefulComponent />
    </div>
  );
}

render2() {
  return (
    <div>
      {this.state.showWarning ? <Warning /> : null}
      <StatefulComponent />
    </div>
  );
}

While they might look the same at first glance, their behaviors can be very different.

Let's start with the first example. When the value of this.state.showWarning changes, <StatefulComponent> will always unmount and then remount a new instance. This is almost always undesirable. Let's take a closer look why this happens.

Pass12
this.state.showWarningfalsetrue
render1 div children[<StatefulComponent>][<Warning>, <StatefulComponent>]
render2 div children[null, <StatefulComponent>][<Warning>, <StatefulComponent>]

With the render1's output, React will compare the <StatefulComponent> with the <Warning> and notice that they have different types. Therefore, React will decide that this pair of components should perform an unmount and mount (unmount the <StatefulComponent> and mount the <Warning> in its place). React will then mount the “new” <StatefulComponent> because there is no previous component in the same place.

render3() {
  if (this.state.showWarning) {
   return (
     <div>
       <Warning />
       <StatefulComponent key="a" />
     </div>
   );
  }

  return (
    <div>
      <StatefulComponent key="a" />
    </div>
  );
}

This implementation will have the same desirable behavior of render2, although it is a little harder to read. This works because React will reconcile the <StatefulComponent> with each other because they have the same keys.

_
Read Another

Component Lifecycle

Components have lifecycles like you and me. Understanding the different lifecycle stages will help you do more with React.

Liked this?

Subscribe

Subscribe for a range of articles from React basics to advanced topics such as performance optimization and deep dives in the React source code.