Preface
My last article about the obscured history behind Hyperscript was warmly received, so I decided to pursue the matter...
Have you ever found yourself in this annoying position where you are not exactly sure how things work under the hood? If you are a tinkerer, chances are it is almost a habit for you. So let's break it together!
As a frontend developer, I can't find better technology to examine rather than React. Its codebase is big enough, well-tested, and filled with great code to learn from. Also, it's simply a really big deal for a frontend developer to understand how React works behind the scene.
You’ve probably seen lots of articles and videos about React internals, so what’s the point in another one?
Code-intense
We'll take a deep dive into the codebase, not into the React-related concepts. Of course, we will examine necessary concepts, but through their application in the source code.
Practice-based
I’ll try to do my best to introduce as much practice-oriented content, as I possibly could to facilitate improvement in real-world coding skills. We will make our own versions of different parts of React, play around with its guts and hack them.
Live series
I’ve planned to create a weekly series to divide and conquer the difficult task of investigating React internals. So we may take a thorough look and grasp the very nature of React source code.
Enough promises, fasten your seat belts our journey begins now!
Disclaimer: This article turned out to be quite long. So if you had a hard time reading it, let me know in the comments section, and I will split up future articles into smaller chunks.
Prerequisites to the whole series
In this article, we'll sum up the needed fundamentals about React and its codebase to get our feet wet and lay the foundation for further investigations in future articles.
Where did React come from?
To understand the source code of React we need to understand why it was initially created. In other words, why source code was written in the first place?
Extra material: I got info for this paragraph from this YouTube video, which was captured on the first React.js Conf in 2015. The video is filled with a ton of interesting details about the early days of React. If you are interested in extra info, check it out from 1:29 to 17:15.
The starting point
Back in 2011 developers at Facebook Ads Org wrote client-side JS applications following an MVC pattern and using two-way data binding along with templates. These apps were initially pretty simple, but they got more complicated over time because more features were added.
So to maintain and extend these apps more developers were hired. Eventually, the growing number of app features and team members slowed down the whole development of these apps too much.
The main problem
The main technical issue was so-called cascading updates. When some new data would flow in the app, some small change somewhere deep in the tree would cause a full re-render of the whole app from scratch. These updates were really slow because not only do you have to figure out what needs to be mutated, but you need to go and get the views to update themselves. Such updates not only hurt performance, but they added up over time and developers couldn't get their heads around to figure out what would cause the cascading updates in the first place.
Sidenote: If you want to code an app with cascading updates in place to trace the problem, that led to React creation, let me know in the comments. I'll attempt to figure things out even more and make another tutorial-like article, where we would create an MVC client-side app like it's 2011 and we are a group of engineers at Facebook Ads Org.
The solution
The 'aha' moment happened when the team realized, that they already had code that describes what the app should look like with a given data. So conceptually, when changes happened, they could just re-execute the logic once again with a new set of data.
The only major problems with this approach were performance and glitches. Performance was bad because a full re-render is a CPU-intense calculation and glitches were a thing before different states (e.g. selection state) were lost when re-render happened.
To resolve these issues, Jordan Walke built a prototype, that made this whole process more efficient and provided a reasonable user experience. He didn't have a name for it yet, but that's actually when React was born.
Well, this was a long story short of why React was created and now we understand the idea behind React.js.
Extra material: If you are eager to develop an even deeper understanding of the story behind React creation, check out this history timeline article by RisingStack Engineering team. The article contains all needed links and explanations, that you can follow to fully understand why React was born.
Let's pick the version to examine
React source code lives in the official repository on GitHub.
If you want to examine code along with me and thus maximize benefits from this article series, you may clone this repo to your local machine by running:
git clone https://github.com/facebook/react.git
Enter fullscreen mode
Exit fullscreen mode
We’ll inspect the latest stable major release of React by the date of this article publication - v17.0.0.
Extra material: If you don't fully understand what major release means or why there are three numbers in the version scheme, check out this great article provided by GitKraken. It covers semantic versioning as a concept and how to handle it in the real codebase with git tags.
React team uses git tags for versioning. So let's check out the commit, where Dan Abramov bumped packages' versions for the 17.0.0 release.
git checkout v17.0.0
Enter fullscreen mode
Exit fullscreen mode
Now we are all on the same page, so let's guide our way to the actual code.
Let's make sense of the repository setup
React is a monorepo, which means multiple projects (or packages in the case of React), that related somehow live in the single repository. According to the official React docs, the monorepo pattern is used to coordinate changes between different React packages and host issues in one place.
Extra material: If you are really interested in finding out more valuable info about monorepo and figuring out the advantages of this pattern, check out this article by Dan Luu.
All React packages live in the ./packages
directory and we’ll go through the primary ones in this article to get a bird’s-eye view of React as a project is made up of.
Sidenote: We’ll take a thorough look at all major packages in future articles.
React Core
Located in ./packages/react
.
This package contains only the functionality necessary to define React components, so it doesn’t render anything on its own. This exact package is available on npm as a react
package.
So when you do something like so:
import React from 'react';
Enter fullscreen mode
Exit fullscreen mode
You effectively refer to the React Core package, not to the whole React repo.
Renderers
As we already know, React was originally created for the client-side JS applications, that run in the browser. But later it was tweaked to also support native platforms, like iOS and Android. This adaption introduced the concept of renderers to React codebase.
Renderers are very important for React because they manage how the output from the React Core package is rendered in different environments.
Sidenote: We'll write our own renderer in one of the future articles!
So there are multiple renderers in the repo now.
First of all, let's look at three renderers, that actually render React components to the user.
react-dom
Located in ./packages/react-dom
It's a descendant of the original React, which was tightly coupled with the browser environment. It renders React components to the DOM and this exact package is available as react-dom
npm package.
So you're probably familiar with its simplest usage:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Enter fullscreen mode
Exit fullscreen mode
react-native-renderer
Located in ./packages/react-native-renderer
It interacts with React Native implementation, which renders React components to native views. So unlike react-dom
, it's less a renderer and more a layer between React Core and React Native.
react-art
Located in ./packages/react-art
It provides bindings to the ART library, which is used for drawing vector graphics. It's the third officially supported renderer, aside from renderer for DOM and native platforms.
Then, let's quickly review renderers, that are used for purposes other than rendering React components to the user screen.
react-test-renderer
Located in ./packages/react-test-renderer
This renderer was created together with Jest team.
It renders React components to JSON tree and makes Snapshot Testing possible.
If you've written tests before, you are already familiar with this application of react-test-renderer
.
We define a component.
// Link.js
const Link = ({to, children}) => {
return <a href={to} target="_blank" className="link">{children}</a>
}
export default Link;
Enter fullscreen mode
Exit fullscreen mode
Then we write a test for it.
import renderer from 'react-test-renderer';
import Link from './Link';
test('Link component renders correctly', () => {
const tree = renderer
.create(<Link to="https://dev.to">DEV community</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
Enter fullscreen mode
Exit fullscreen mode
When we run the test, Jest creates a snapshot file, that contains the output of the component.
exports[`Link component renders correctly 1`] = `
<a
className="link"
href="https://dev.to"
target="_blank">
DEV community
</a>
`;
Enter fullscreen mode
Exit fullscreen mode
On the next test run, Jest will compare a new output with the previous one. If they match, the test will pass. If they don't match, the test will fail, indicating that a bug was introduced.
The react-test-renderer
is very important to React ecosystem because it allows making sure that React components don't change unexpectedly.
react-noop-renderer
Located in ./packages/react-noop-renderer
This renderer is used by React team for debugging, the so-called Fiber Reconciler, and is not intended to be used directly by design.
Sidenote: If you don't know or are just not exactly sure what Fiber Reconciler is, keep reading, We'll take care of this in a snap.
Reconcilers
Reconcilers are a major part of React library.
They implement the reconciliation algorithm, that makes React fast enough for real-world apps.
If the reconciliation algorithm wasn't a thing, either we, as developers, would have to manually update our components, or React would have to re-render the whole app on every minor change in any given component.
React v17.0.0 uses Fiber Reconciler, which was introduced in React v16.0.0. Simply put, Fiber Reconciler is a package, that implements the reconciliation algorithm in a certain way.
Its source code is located in ./packages/react-reconciler
.
We'll take a closer look at the Fiber Reconciler in one of the next episodes of the series.
Sidenote: Why do I refer to reconcilers as a plural, nor singular noun, though only the Fiber Reconciler is out there? It's because the Fiber Reconciler is a replacement for the so-called Stack Reconciler, that powered React v15.0.0 and earlier.
If you want to understand the difference between Fiber and Stack Reconcilers, let me know in the comments and I'll consider making a series of articles, where we would take a deep dive into both reconcilers and code their simpler versions ourselves.
Afterword
Oof, this was a long and intense reading and we learned a ton about React as a project and more specifically React codebase.
In the next episode, we'll continue to explore the repository setup and have fun in the process!
Sum up
Let's sum up what we learned today.
- React was created, because Facebook developers couldn't extend and maintain complex apps with dynamic data in MVC style.
- React came from a conceptually simple idea - if you have code, that describes UI with a given set of data, you may re-execute this exact code if data have changed.
- React is a monorepo, that contains React Core, a bunch of renderers, and a reconciler.
- React Core contains only the functionality necessary to define React components and is available through the
react
npm package.
- Renderers manage how React components are rendered in different environments.
- Reconciler implements a reconciliation algorithm, that allows React to be declarative and fast.
I'am looking forward to the next episode, what should I do?
It's a weekly series and I am going to publish the next episode of the series on Sunday (January 15) at 6:00 am UTC+0.
Follow me on dev.to or Twitter, if you don't want to miss it.
If you have any ideas regarding this series or any other technology you want to make sense of, let me know in the comments!
Any feedback on this episode, either positive or negative will be welcomed.