DEV Community

Aiman Jaffer
Aiman Jaffer

Posted on

React Internals: How React works

This article will attempt to formalize my understanding of how the React library work internally to allow us to create complex web-applications. Without any further ado, let's dive right into it.


HTML and the DOM

Let's start with two terms that you will encounter a lot in web development: HTML and DOM. HTML stands for Hypertext Markup Language. A web-page is simply a document written in HTML.

DOM stands for Document Object Model. The DOM is the representation of the web-page that is understood by the web-browser. The DOM is represented as a tree of HTML tags. (In a Tree data structure, each node can have zero or more child-nodes. The child-nodes belonging to the same parent are known as siblings and nodes without children are known as leaf nodes.)

The DOM provides APIs through which we can make changes to the web-page. For example using methods like document.getElementById etc.


Components

React is a Component-based library. Components are a layer of abstraction in that they allow us to describe the UI we wish to generate while leaving the implementation details to React.
React Components can be created in two ways:
1) Using Classes
2) Using Functions

While using Class-based components we use the render() method to define the JSX that should be displayed on the browser whereas while using Functional components we directly return the JSX that should be rendered as the result of the function.


JSX and React.createElement

JSX allows you to write HTML code directly inside JavaScript functions. It is syntactic sugar for the method React.createElement().

JSX is converted to regular JavaScript via a transpiler known as Babel (using the preset babel/preset-react).

Let's examine this in detail using an example:
This is a simple Component that I already had from a hobby project:

export default function PlaylistDetail(props){
    return (
        <div>
        <p>Playlist Name</p>
        <p>Created by</p>
        <div>Track List</div>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Compiling this code using Babel produces the following output:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = PlaylistDetail;

function PlaylistDetail(props) {
  return /*#__PURE__*/ React.createElement(
    "div",
    null,
    /*#__PURE__*/ React.createElement("p", null, "Playlist Name"),
    /*#__PURE__*/ React.createElement("p", null, "Created by"),
    /*#__PURE__*/ React.createElement("div", null, "Track List")
  );
}
Enter fullscreen mode Exit fullscreen mode

From this, we conclude that JSX tags are ultimately converted to calls to the React.createElement() method. Now you may be thinking: That's great! But why should I care? We'll get to that in a bit. Hang tight, all of this will make a lot more sense in a bit, I promise. We just need to understand what an Element is in React.


Elements and Component Instances

An Element in React is a simple JavaScript object that describes the instance of a component. Think of Components as being general and Component instances as specific (i.e. A Component with specific props and state). Whenever the props and state of a Component change it results in a different instance of the Component.

The Element object representing the instance of the PlaylistDetail Component seen previously is shown below:

{
  $$typeof: Symbol(react.element),
  key: null,
  props: {
    children: [
      {
        $$typeof: Symbol(react.element),
        key: null,
        props: {
          children: "Playlist Name"
        },
        ref: null,
        type: "p"
      },
      {
        $$typeof: Symbol(react.element),
        key: null,
        props: {
          children: "Created by"
        },
        ref: null,
        type: "p"
      },
      {
        $$typeof: Symbol(react.element),
        key: null,
        props: {
          children: "Track List"
        },
        ref: null,
        type: "div"
      }
    ]
  },
  ref: null,
  type: "div"
}
Enter fullscreen mode Exit fullscreen mode

The $$typeof field is used to prevent Cross site scripting Attacks (XSS) since it should always contain a Symbol which prevents the JavaScript object from being parsed from plain JSON objects.

The key field is used to optimize performance by allowing elements generated from a list to be uniquely identified.

The props field is an object containing all the props that are passed to the Component. The props field also stores children which is an array containing the children of the current element.

The type field refers to the type of the object either a native HTML tag or a Component type. In this example the type is a native HTML div.

The ref field stores a reference to the actual DOM node. (This can be utilized via the useRef hook).

Thus, we see that React components (and their child components) can be represented as objects (with nested objects) similar to the way the web-page is itself represented via the tree structure of the DOM. Hence this representation of React components as a tree of objects is known as the Virtual DOM.


Why bother?

The reason React goes through this cumbersome process of maintaining object representations of Components is simple: Performance. It's computationally much cheaper to create and manipulate JavaScript objects than it is to manipulate the DOM.

This allows React to consolidate all the changes that need to be made in a render cycle and perform one bulk update to the DOM instead of performing many smaller changes.


Reconciliation

Now, let's take a look at how React determines when a Component needs to be re-rendered and what exactly has changed since the previous render cycle. This information is required in order to prevent unnecessary DOM updates. (i.e. updating DOM nodes that have not been changed since the previous render)

The process of maintaining the tree of Elements whenever a Component's props or state change is known as Reconciliation. React does this by maintaining the state of the Element tree from the previous render and comparing it to the new Element tree using an diffing algorithm.

The problem of comparing the previous tree (or graph) with the new tree is known in Computer Science as the Graph Edit Distance Problem.

Theoretically, this problem falls under a class of problems known as NP-hard (non-deterministic polynomial-time) but there are approximation solutions to this problem in O(n^3). React's diffing algorithm uses certain optimizations that further reduce the runtime to O(n). (n is number of nodes in the tree).


Optimizations, you say?

The optimizations React uses to achieve this performance boost are:

  1. If an element's type field has changed, then React re-creates the entire subtree (for this particular element and it's children).
  2. React uses the key field to uniquely identify elements generated dynamically from a list in order to prevent recreating the entire list when list operations are performed (E.g. when elements added or deleted, or list is reversed etc.). React simply makes the relevant changes directly in the DOM.
  3. When attributes of native DOM elements are changed but the element type remains the same then only the attributes that have changed are updated (instead of re-creating the entire DOM Node).

Rendering vs Reconciliation

The process of Reconciliation (that we looked at in the previous section) determines what changes need to be made to the DOM.

The process of actually making these changes is known as Rendering. There is a separation of concerns between the packages responsible for Reconciliation and Rendering.

The react package is only concerned wit Reconciliation.
The react-dom package is concerned with actually performing the rendering on the browser. It does this via the ReactDOM.render() method.

This separation of concerns allows react-native to replace react-dom while rendering to mobile apps.


TO BE CONTINUED

How does Rendering work? Fibers

How Rendering previously worked with React Stack? (before v16) Stack vs LinkedList

Latest comments (0)