We all know we can create React components using JSX and then render them on the web using React DOM or in a mobile app using React
Native. We also have access to incredible features like the state or the component lifecycle and finally, it is all extremely fast and flexible, but what is actually happening behind the scenes?
In this article, we will go over the basic concepts that make React what it is.
We will focus on reconciliation and rendering, the virtual DOM, the React Diffing algorithm, and much more.
First, we have to understand the difference between React components, elements, and component instances.
Let’s start by taking a look at a simple component. I think this should be very familiar to you.
that looks like this.
If you want to check for yourself, you can just log it to the console. When we call the component, we get the real return value.
So, what exactly is happening?
- First, the JSX gets converted to many React.createElement function calls.
- Then, each of them returns the appropriate object.
The fact that JSX gets converted to the React.createElement functions is the reason why we always have to import React when using JSX. Now, let’s take a close look at the return value.
In our case, it is an object that represents the root element, a div. The div is a type property, which corresponds to an HTML element. We can also see the key and ref properties, which are both null because we haven’t provided any of them. There are also the familiar props and they
contain all of the children. In our case, it is a simple text of “App component”.
You might wonder how does function React.createElement look like in practice. Well, let’s just head over to https://babeljs.io/repl, and here, we pass in the App component.
Then, on the right side, we can see the converted code. We can see the React.createElement function being called with some arguments. The first argument is the element type, the second is the props, and the third argument is the children.
Now, we should have a good understanding of what a React element is.
So, what is a React component?
A React component is a class or a function that outputs an element tree. If it is a function, the output is the return value of the function, and if it is a class, the output is the return value of the render method.
Not only do React components have an output, but they also have an input, which we all are familiar with – the props. When we render our component in the JSX, React is in fact calling it behind the scenes. If it is a function, React calls it directly with the assigned props and if it is a class, React creates a new instance of the class and calls its render method.
So not only can React elements describe a DOM node, but they can also describe a component, more precisely a component instance.
But what is a component instance?
When a React element describes a component, React keeps track of it and creates a component instance. Each instance has a state and lifecycle and
those instances are in fact very familiar to us.
In functional components, we can access the state and the lifecycle using React Hooks and in the class components, we make use of predefined methods and the “this” keyword, which refers to the individual component instance. The same way React calls our components, it also manages our instances by mounting and unmounting them.
So now, we should have a pretty good knowledge of React elements, components, and component instances.
Now, it’s time to move to reconciliation.
All of this is happening, when we call the render method. React generates this tree of elements by starting at the very top and recursively moving to the very bottom. If it encounters a component, it calls it and keeps descending down to the returned React elements.
React keeps this tree of elements in memory, it is called the virtual DOM. The next thing to do is to sync the virtual DOM with the real DOM. On the initial render, React has to insert the full tree into the DOM. Making those kinds of adjustments to the DOM is very expensive, but on the initial render, there is no way around it.
But what if the tree changes?
What if there is a state change that results into a different return value and different elements? React once again very quickly generates a new tree of elements and we now have two trees – the old one and the new one.
Now, it has to once again sync the virtual DOM containing the new tree with the real DOM. It would be very inefficient to re-render the whole tree because as we know, making changes to the DOM is quite demanding.
And this is why Reacts takes the old tree and finds the smallest number of operations to transform it to the new tree. It uses an algorithm called the Diffing algorithm in order to do that, diffing, because the goal is to differentiate the trees. Under normal circumstances, this is would be very difficult.
For example, if we had one thousand elements, it could take up to one billion operations, which is not realistic. React instead manages to do that in a number of operations smaller than the number of elements. So, for one thousand elements, the number of operations will be smaller than one thousand. This is incredibly fast and it is possible only because of two very important assumptions.
Let’s talk about those now.
First, it assumes that two different types of elements will produce two completely different trees. You might be looking at this and saying “But isn’t this obvious?” and, generally speaking, not so much, but in the context of a React application, we can safely assume that two different element types will indeed result in two very different trees.
The second assumption is simpler. React assumes then when we have a list of child elements, which we make changes to, we will provide the key and we will provide it in the right way.
Let’s take a look at an example. Say we have a list of items that we map over and we have to provide unique keys.
The first idea you might have is to provide keys using indexes.
But this is one of the infamous bugs that I’m sure most of us came across at some point. This leads to unexpected functionality and
it shouldn’t be surprising at all. We know that keys should always stay the same.
Therefore, if we add a new item to the list to the very beginning, the keys will all increase by 1 which is wrong. This is why we provide keys using ids or simply
Now, you should understand why – key is unlike any other property. It is an important part of the very core of React – the Diffing algorithm.
Now, let’s move to the rendering itself.
This is where packages like React DOM and React Native come into play. We call those packages renderers. React on its own is just a library that allows us to define components and elements and it also does the diffing part. It isn’t platform-specific and doesn’t take care of the rendering. Most of the actual implementation lives in the renderers.
Renderers begin the reconciliation process. They generate the tree of elements and insert it wherever it has to be inserted. This is why they are a different package. When we use React for web development, React basically doesn’t even know that the elements end up in a web browser.
React is therefore compatible with any renderer and you can in fact create your own using the react-test-renderer package.
Once again, just to emphasize, React on its own only gives us the means of expression, so we can define components and so on, and it also does the diffing part. When you think about this, it may appear quite weird to you. In terms of web development, we usually only import React DOM once and call its render method.
ReactDOM.render( <App/>, document.getElementById('root') )
So how can most of the implementation live in the renderer package? The key to understanding this is to know that React communicates with the renderer. There are many ways of React doing that.
What about React hooks?
React hooks basically do the same thing, however, instead of an updater field, they use a dispatcher object. Internally, it looks similarly to this.
We can see that when we call useState, the call is forwarded to something called currentDispatcher. And this dispatcher is once again set by React DOM. Whenever we render a component, React DOM sets the dispatcher similarly to this. This time around, the component is called, because it is a function. You can read in detail about UseState hook in this article: How to use the “useState” hook in React Js?
So, this is how React communicates with the renderers.
And that’s all I wanted to mention in this article.