27 January 2022
How does React work under the hood? Find out and become a better developer
React is without a doubt one of the most powerful and flexible frontend frameworks. But with its great power comes great responsibility – it’s easy to misuse it and create a mess of an app. To avoid it, one needs to really understand how React works internally by studying its code implementation and structure. Coincidentally, it is also one of the best ways to learn React in general. Let’s do this today!
Working with a non-opinionated framework such as React gives you a lot of freedom, but also introduces more room for mistakes. Wait a second… What’s the deal with opinionated and non-opinionated frameworks?
Taming a non-opinionated framework
During my career as a frontend developer, I’ve mostly been building user interfaces with two very popular frameworks: React and Vue (both placed in the top 10 of all GitHub-hosted projects with 181k and 192k stars respectively). What captivated me the most about these frameworks was the flexibility and freedom in web development that they had to offer.
Back then I also had the opportunity to work with Ember.js – a very pleasant framework with a great Ember Data feature.
One sentence from the documentation delighted me:
Ember’s implementation of components hews as closely to the Web Components specification as possible
I love native solutions. I love web components and I hope that one day they will gain more popularity. But that’s not the point. Ember, similar to Angular, is one of the so-called opinionated frameworks, which give you a lot of instructions and foundation at the cost of freedom. An opinionated framework is like a safety net that lowers the learning curve, taking away much of the flexibility in the process. React and Vue are quite the opposite.
With great power comes great responsibility
On the backend side, things are much the same. We have a non-opinionated framework like Express. On the other hand, we have Nest.js.
You can use both to create amazing apps. Nest.js is a great framework that has a lot to offer. Express provides simplicity and flexibility that allows you to build… a horrible, awful API if you are not using it wisely. That brings another factor to the game – embracing curiosity, eagerness to search for the right solution, and exposure to the whole world of patterns, concepts, and solutions that the industry has to offer.
If there was one thing that I would like you to take out of this post is this:
Build things, experiment, and have fun with them. Learn by doing, and take a look under the hood of the library that you are using. That way, you will even be able to take control of a non-opinionated framework like React.
With that said, let’s get to it!
My First React-like “framework”
Let’s take a look under the hood of the React library. Pretty much everyone (and their dog) knows the basics and this piece of code should be familiar to anyone who knows what React is. This is the Hello World of React:
The signature of the function looks as follows:
The implementation of createElement looks like this:
The implementation is stripped down from fancier functionalities such as refs and keys. It focuses only on the core functionality. This is as close to the original implementation as it could be.
The final piece of the puzzle is a render function. This is the simplest, most bare-bones implementation that would target the web environment:
First, we create a DOM element:
Then, we set all necessary properties. Again, this is something that you can find in the React code base, except this example is more simplified.
For the sake of completeness, here are the two helper functions:
Now, what’s left is to go through all children elements recursively and mount everything to our root element. And voila – our first React “app”!
This is a ridiculously simple example. Check out a much more sophisticated one from Rodrigo Pombo.
What’s in the box?
That was a very naive implementation. It allowed us to look at React from a slightly different angle, but what is React really made of?
What we know as React actually consists of a couple crucial React elements:
- React – the core,
React includes all the API necessary to define React components. It is platform-agnostic. This is the place where you can find createElement.
The renderer is the part of the React ecosystem responsible for displaying React components on specific platforms (Web, Mobile, CLI). Underneath, all renderers share some common logic. That shared part is encapsulated in the Reconciler. This is the core algorithm that is independent from any platform.
There are three officially supported renderers: React Dom, React Native, and React Test. In addition, there are many custom renderers. To learn more about those, check out Awesome React Renderer.
You can even build your own renderer with this great guide to building a custom React renderer.
Earlier versions of React were powered by the so-called Stack Reconciler. It has been in use up to version 15 of React. With the release of React 16, we received a greatly improved reconciler algorithm, the Fiber Reconciler.
Perhaps you are familiar with the visual comparison of both reconcilers and the great performance enhancement offered by the latter?
If you are interested in Stack Reconciler and want to read more about it, I highly recommend you read the official Implementation Notes, where you can find a very technical introduction.
At the time of writing this article, we have React version 17.0.2 as the latest stable version. Recently, we received the v.5.0.0 version of Create React App with the updated Webpack version. This is great news because I’m a big fan of Module Federation, which is a Webpack 5 feature.
Now, the new version of React is coming out very shortly. With version 18 (which is in beta), we will finally receive a much-anticipated stable version of Concurrent Mode.
What is so special about that, you might ask? It gives us a huge boost of performance and is another step forward after switching from Stack Reconciler to Fiber Reconciler.
Concurrent Mode has been available for a while in the experimental version, but what is it exactly?
Concurrent Mode is a set of new features that help React apps stay responsive and gracefully adjust to the user’s device capabilities and network speed. – Introducing Concurrent Mode (Experimental)
Back then, I made some effort and created a couple of demo projects for The Software House’s popular meetup – Uszanowanko Programowanko.
I hope that after all this time the code is still relatively fresh 😉
Apart from all the great features available with Concurrent Mode, the main difference between current React and concurrent-mode React is blocking rendering vs interruptible rendering. The crucial distinction is in how the render phase is handled.
Without changing the reconciliation algorithm, interruptible, asynchronous rendering would not be possible. Switching from Stack Reconciler, where the entire process was done recursively without the possibility of interruption, to the Fiber Reconciler, where the render phase can be interrupted, is fundamental for all the interesting features that Concurrent Mode has to offer.
A stable production-ready version of React with Concurrent Mode is right around the corner. It’s been in beta since November 15th, 2021. You should play with the Concurrent Mode API while it is still at this stage. This is a really great feature that will improve the user experience.
Read more about: concurrent rendering and other new features introduced in React 18
Render vs Commit Phase
React is a UI library. A very important goal of any UI library is to present consistent predictable user interfaces to the user. That’s what makes React apps really out. To ensure that, the document object model is updated at once in a commit phase without any partially changed UI elements. The render phase is where the reconciliation mechanism is applied (the so-called Virtual DOM that manipulates real DOM). That’s where React does the whole heavy lifting work.
The Work Loop
This is the work loop in React version 17. All the work is performed synchronously.
In the experimental version and for the next major release, the work loop can be interrupted.
Once you gather every important part in one place and use a kind of magic called simplification, the work loop might look like that:
As long as there is some unit to work with, React is in the render phase. After all, this is done and we have our alterated tree, everything is committed to the DOM.
The requestIdleCallback method is responsible for the interruption part. Of course, React does not rely on requestIdleCallback, especially since some browsers do not have this feature implemented (I’m looking at you, Safari). There is a special library responsible for that called Scheduler. When it comes to requestIdleCallback, the IdleDeadline object is passed to the callback function. It provides a method to check the remaining time that we have to perform our task without blocking the user interface.
A side note – binary heap
If you asked me what the best part of being a developer is, I would say that it is the opportunity to learn new stuff each day. Just as an example, when you go through the codebase of React and focus on the Scheduler package, you can encounter the implementation of the binary heap (min-heap to be more precise):
If you don’t know anything about the binary heap, learning more about it might be the trigger that will drive you to improve your skills and learn new things.
Interested in the binary heap? Here’s a great lecture on that:
Back in the loop
Let’s perform some work on the Fiber tree linked list data structure in the Fiber Reconciler.
If you follow the issues on the React repository, you can find a lot of great materials worth reading. In particular, I highly recommend you read Fiber Principles: Contributing To Fiber, where Sebastian Markbåge opens a very informative issue about a singly linked list tree traversal algorithm.
React keeps current Fiber tree and work-in-progres tree. The first one reflects the current state of the UI presented to the user. The other one is built whenever some change happens. As stated before, after all that work is done (which can be interrupted if the browser needs to take over the control), everything is rendered to the DOM.
Summary, i.e. “what does React do” vs “how does React work”
And that’s it for today! We went over quite a few issues in a short time.
- We talked about opinionated and non-opinionated frameworks,
- took a look at the structure of React and the relationship between its various parts,
- inspected the Concurrent mode,
- explored various other subjects such as React at the commit phase, work loop binary heap, or Fiber Reconciler.
Feeling somewhat enlightened? Well, you should be if you never looked at React that way. Learning about how React works internally rather than just using it to complete tasks is important. It makes it much easier for you to solve even the most complex issues and address any problems and simply makes you an all-around better programmer. If you want to advance through the ranks and become a top React developer, it may even be inevitable.
My main goal is to encourage you to read the code, try to know more and more about the tools that you are using, and not just automatically install all the available npm libraries.
Learn and experiment. Learn by doing. Not so long ago, I was helping my colleagues with the configuration of Webpack for some React project. Then, my holiday kicked off and I was not able to help them. They needed to figure this out on their own and they managed this splendidly, because they learned how React works.
There is no better way to learn new things than trying them on your own.
If you have reached this place, it means that you really like React and frontend.
In that case, take part in our most recent State of Frontend survey. The new report will be a follow-up t0 the most popular frontend report in the world!