This section will give you an overview of the React codebase organization, its conventions, and the implementation.
If you want to contribute to React we hope that this guide will help you feel more comfortable making changes.
We don't necessarily recommend any of these conventions in React apps. Many of them exist for historical reasons and might change with time.
At Facebook, internally we use a custom module system called "Haste". It is similar to CommonJS and also uses require()
but has a few important differences that often confuse outside contributors.
In CommonJS, when you import a module, you need to specify its relative path:
// Importing from the same folder:
var setInnerHTML = require('./setInnerHTML');
// Importing from a different folder:
var setInnerHTML = require('../utils/setInnerHTML');
// Importing from a deeply nested folder:
var setInnerHTML = require('../client/utils/setInnerHTML');
However, with Haste all filenames are globally unique. In the React codebase, you can import any module from any other module by its name alone:
var setInnerHTML = require('setInnerHTML');
Haste was originally developed for giant apps like Facebook. It's easy to move files to different folders and import them without worrying about relative paths. The fuzzy file search in any editor always takes you to the correct place thanks to globally unique names.
React itself was extracted from the Facebook codebase and uses Haste for historical reasons. In the future, we will probably migrate React to use CommonJS or ES Modules to be more aligned with the community. However, this requires changes in Facebook internal infrastructure so it is unlikely to happen very soon.
Haste will make more sense to you if you remember a few rules:
require('./setInnerHTML')
, write require('setInnerHTML')
.When we compile React for npm, a script copies all the modules into a single flat directory called lib
and prepends all require()
paths with ./
. This way Node, Browserify, Webpack, and other tools can understand React build output without being aware of Haste.
If you're reading React source on GitHub and want to jump to a file, press "t".
This is a GitHub shortcut for searching the current repo for fuzzy filename matches. Start typing the name of the file you are looking for, and it will show up as the first match.
React has almost no external dependencies. Usually, a require()
points to a file in React's own codebase. However, there are a few relatively rare exceptions.
If you see a require()
that does not correspond to a file in the React repository, you can look in a special repository called fbjs. For example, require('warning')
will resolve to the warning
module from fbjs.
The fbjs repository exists because React shares some small utilities with libraries like Relay, and we keep them in sync. We don't depend on equivalent small modules in the Node ecosystem because we want Facebook engineers to be able to make changes to them whenever necessary. None of the utilities inside fbjs are considered to be public API, and they are only intended for use by Facebook projects such as React.
After cloning the React repository, you will see a few top-level folders in it:
src
is the source code of React. If your change is related to the code, src
is where you'll spend most of your time.docs
is the React documentation website. When you change APIs, make sure to update the relevant Markdown files.examples
contains a few small React demos with different build setups.packages
contains metadata (such as package.json
) for all packages in the React repository. Nevertheless, their source code is still located inside src
.build
is the build output of React. It is not in the repository but it will appear in your React clone after you build it for the first time.There are a few other top-level folders but they are mostly used for the tooling and you likely won't ever encounter them when contributing.
We don't have a top-level directory for unit tests. Instead, we put them into a directory called __tests__
relative to the files that they test.
For example, a test for setInnerHTML.js
is located in __tests__/setInnerHTML-test.js
right next to it.
Even though Haste allows us to import any module from anywhere in the repository, we follow a convention to avoid cyclic dependencies and other unpleasant surprises. By convention, a file may only import files in the same folder or in subfolders below.
For example, files inside src/renderers/dom/client
may import other files in the same folder or any folder below.
However they can't import modules from src/renderers/dom/server
because it is not a child directory of src/renderers/dom/client
.
There is an exception to this rule. Sometimes we do need to share functionality between two groups of modules. In this case, we hoist the shared module up to a folder called shared
inside the closest common ancestor folder of the modules that need to rely on it.
For example, code shared between src/renderers/dom/client
and src/renderers/dom/server
lives in src/renderers/dom/shared
.
By the same logic, if src/renderers/dom/client
needs to share a utility with something in src/renderers/native
, this utility would be in src/renderers/shared
.
This convention is not enforced but we check for it during a pull request review.
The React codebase uses the warning
module to display warnings:
var warning = require('warning');
warning(
2 + 2 === 4,
'Math is not working today.'
);
The warning is shown when the warning
condition is false
.
One way to think about it is that the condition should reflect the normal situation rather than the exceptional one.
It is a good idea to avoid spamming the console with duplicate warnings:
var warning = require('warning');
var didWarnAboutMath = false;
if (!didWarnAboutMath) {
warning(
2 + 2 === 4,
'Math is not working today.'
);
didWarnAboutMath = true;
}
Warnings are only enabled in development. In production, they are completely stripped out. If you need to forbid some code path from executing, use invariant
module instead:
var invariant = require('invariant');
invariant(
2 + 2 === 4,
'You shall not pass!'
);
The invariant is thrown when the invariant
condition is false
.
"Invariant" is just a way of saying "this condition always holds true". You can think about it as making an assertion.
It is important to keep development and production behavior similar, so invariant
throws both in development and in production. The error messages are automatically replaced with error codes in production to avoid negatively affecting the byte size.
You can use __DEV__
pseudo-global variable in the codebase to guard development-only blocks of code.
It is inlined during the compile step, and turns into process.env.NODE_ENV !== 'production'
checks in the CommonJS builds.
For standalone builds, it becomes true
in the unminified build, and gets completely stripped out with the if
blocks it guards in the minified build.
if (__DEV__) {
// This code will only run in development.
}
Some of the internal and public methods are annotated with JSDoc annotations:
/**
* Updates this component by updating the text content.
*
* @param {ReactText} nextText The next text content
* @param {ReactReconcileTransaction} transaction
* @internal
*/
receiveComponent: function(nextText, transaction) {
// ...
},
We try to keep existing annotations up-to-date but we don't enforce them. We don't use JSDoc in the newly written code, and instead use Flow to document and enforce types.
We recently started introducing Flow checks to the codebase. Files marked with the @flow
annotation in the license header comment are being typechecked.
We accept pull requests adding Flow annotations to existing code. Flow annotations look like this:
ReactRef.detachRefs = function(
instance: ReactInstance,
element: ReactElement | string | number | null | false,
): void {
// ...
}
When possible, new code should use Flow annotations.
You can run npm run flow
locally to check your code with Flow.
React was originally written in ES5. We have since enabled ES6 features with Babel, including classes. However, most of React code is still written in ES5.
In particular, you might see the following pattern quite often:
// Constructor
function ReactDOMComponent(element) {
this._currentElement = element;
}
// Methods
ReactDOMComponent.Mixin = {
mountComponent: function() {
// ...
}
};
// Put methods on the prototype
Object.assign(
ReactDOMComponent.prototype,
ReactDOMComponent.Mixin
);
module.exports = ReactDOMComponent;
The Mixin
in this code has no relation to React mixins
feature. It is just a way of grouping a few methods under an object. Those methods may later get attached to some other class. We use this pattern in a few places although we try to avoid it in the new code.
Equivalent code in ES6 would like this:
class ReactDOMComponent {
constructor(element) {
this._currentElement = element;
}
mountComponent() {
// ...
}
}
module.exports = ReactDOMComponent;
Sometimes we convert old code to ES6 classes. However, this is not very important to us because there is an ongoing effort to replace the React reconciler implementation with a less object-oriented approach which wouldn't use classes at all.
React uses dynamic injection in some modules. While it is always explicit, it is still unfortunate because it hinders understanding of the code. The main reason it exists is because React originally only supported DOM as a target. React Native started as a React fork. We had to add dynamic injection to let React Native override some behaviors.
You may see modules declaring their dynamic dependencies like this:
// Dynamically injected
var textComponentClass = null;
// Relies on dynamically injected value
function createInstanceForText(text) {
return new textComponentClass(text);
}
var ReactHostComponent = {
createInstanceForText,
// Provides an opportunity for dynamic injection
injection: {
injectTextComponentClass: function(componentClass) {
textComponentClass = componentClass;
},
},
};
module.exports = ReactHostComponent;
The injection
field is not handled specially in any way. But by convention, it means that this module wants to have some (presumably platform-specific) dependencies injected into it at runtime.
In React DOM, ReactDefaultInjection
injects a DOM-specific implementation:
ReactHostComponent.injection.injectTextComponentClass(ReactDOMTextComponent);
In React Native, ReactNativeDefaultInjection
injects its own implementation:
ReactHostComponent.injection.injectTextComponentClass(ReactNativeTextComponent);
There are multiple injection points in the codebase. In the future, we intend to get rid of the dynamic injection mechanism and wire up all the pieces statically during the build.
React is a monorepo. Its repository contains multiple separate packages so that their changes can be coordinated together, and documentation and issues live in one place.
The npm metadata such as package.json
files is located in the packages
top-level folder. However, there is almost no real code in it.
For example, packages/react/react.js
re-exports src/isomorphic/React.js
, the real npm entry point. Other packages mostly repeat this pattern. All the important code lives in src
.
While the code is separated in the source tree, the exact package boundaries are slightly different for npm packages and standalone browser builds.
The "core" of React includes all the top-level React
APIs, for example:
React.createElement()
React.createClass()
React.Component
React.Children
React.PropTypes
React core only includes the APIs necessary to define components. It does not include the reconciliation algorithm or any platform-specific code. It is used both by React DOM and React Native components.
The code for React core is located in src/isomorphic
in the source tree. It is available on npm as the react
package. The corresponding standalone browser build is called react.js
, and it exports a global called React
.
Note:
Until very recently,
react
npm package andreact.js
standalone build contained all React code (including React DOM) rather than just the core. This was done for backward compatibility and historical reasons. Since React 15.4.0, the core is better separated in the build output.There is also an additional standalone browser build called
react-with-addons.js
which we will consider separately further below.
React was originally created for the DOM but it was later adapted to also support native platforms with React Native. This introduced the concept of "renderers" to React internals.
Renderers manage how a React tree turns into the underlying platform calls.
Renderers are located in src/renderers
:
ReactDOM
APIs and is available as react-dom
npm package. It can also be used as standalone browser bundle called react-dom.js
that exports a ReactDOM
global.react-native-renderer
npm package. In the future a copy of it may get checked into the React Native repository so that React Native can update React at its own pace.The only other officially supported renderer is react-art
. To avoid accidentally breaking it as we make changes to React, we checked it in as src/renderers/art
and run its test suite. Nevertheless, its GitHub repository still acts as the source of truth.
While it is technically possible to create custom React renderer, this is currently not officially supported. There is no stable public contract for custom renderers yet which is another reason why we keep them all in a single place.
Note:
Technically the
native
renderer is a very thin layer that teaches React to interact with React Native implementation. The real platform-specific code managing the native views lives in the React Native repository together with its components.
Even vastly different renderers like React DOM and React Native need to share a lot of logic. In particular, the reconciliation algorithm should be as similar as possible so that declarative rendering, custom components, state, lifecycle methods, and refs work consistently across platforms.
To solve this, different renderers share some code between them. We call this part of React a "reconciler". When an update such as setState()
is scheduled, the reconciler calls render()
on components in the tree and mounts, updates, or unmounts them.
Reconcilers are not packaged separately because they currently have no public API. Instead, they are exclusively used by renderers such as React DOM and React Native.
The "stack" reconciler is the one powering all React production code today. It is located in src/renderers/shared/stack/reconciler
and is used by both React DOM and React Native.
It is written in an object-oriented way and maintains a separate tree of "internal instances" for all React components. The internal instances exist both for user-defined ("composite") and platform-specific ("host") components. The internal instances are inaccessible directly to the user, and their tree is never exposed.
When a component mounts, updates, or unmounts, the stack reconciler calls a method on that internal instance. The methods are called mountComponent(element)
, receiveComponent(nextElement)
, and unmountComponent(element)
.
Platform-specific ("host") components, such as <div>
or a <View>
, run platform-specific code. For example, React DOM instructs the stack reconciler to use ReactDOMComponent
to handle mounting, updates, and unmounting of DOM components.
Regardless of the platform, both <div>
and <View>
handle managing multiple children in a similar way. For convenience, the stack reconciler provides a helper called ReactMultiChild
that both DOM and Native renderers use.
User-defined ("composite") components should behave the same way with all renderers. This is why the stack reconciler provides a shared implementation in ReactCompositeComponent
. It is always the same regardless of the renderer.
Composite components also implement mounting, updating, and unmounting. However, unlike host components, ReactCompositeComponent
needs to behave differently depending on user's code. This is why it calls methods, such as render()
and componentDidMount()
, on the user-supplied class.
During an update, ReactCompositeComponent
checks whether the render()
output has a different type
or key
than the last time. If neither type
nor key
has changed, it delegates the update to the existing child internal instance. Otherwise, it unmounts the old child instance and mounts a new one. This is described in the reconciliation algorithm.
During an update, the stack reconciler "drills down" through composite components, runs their render()
methods, and decides whether to update or replace their single child instance. It executes platform-specific code as it passes through the host components like <div>
and <View>
. Host components may have multiple children which are also processed recursively.
It is important to understand that the stack reconciler always processes the component tree synchronously in a single pass. While individual tree branches may bail out of reconciliation, the stack reconciler can't pause, and so it is suboptimal when the updates are deep and the available CPU time is limited.
The next section describes the current implementation in more details.
The "fiber" reconciler is a new effort aiming to resolve the problems inherent in the stack reconciler and fix a few long-standing issues.
It is a complete rewrite of the reconciler and is currently in active development.
Its main goals are:
render()
.You can read more about it in React Fiber Architecture. At this moment, it is still very experimental, and far from feature parity with the stack reconciler.
Its source code is located in src/renderers/shared/fiber
.
React implements a synthetic event system which is agnostic of the renderers and works both with React DOM and React Native. Its source code is located in src/renderers/shared/stack/event
.
There is a video with a deep code dive into it (66 mins).
Each of the React add-ons ships as a separate package on npm with a react-addons-
prefix. Their source is located in src/addons
with the exception of ReactPerf
and ReactTestUtils
.
Additionally, we provide a standalone build called react-with-addons.js
which includes React core and all add-ons exposed on the addons
field of the React
global object.
Read the next section to learn about the current implementation of reconciler in more detail.