The React Source Code: a Beginner’s Walkthrough I
Ever been stuck in a video game? Do you remember reaching for that strategy guide while battling Shadow Link in the Water Temple? In the spirit of a video game walkthrough, this is a journey through the React Core source code so that beginners may achieve a deeper understanding of the tools they use. Many developers understand the what and how but miss the depth that conveys the why of React.
Furthermore, we write better code by reading good code. I know firsthand how intimidating the broad-ranging scope of the React source can be. It is my hope that by building a line-by-line walkthrough of the React Core source, its patterns and nuances will emerge to beginner developers. This knowledge should lead to a better understanding of the code developers write, encourage informed contributions to the community, and ultimately demystify open source projects.
I will try to make no assumptions about the reader, other than a familiarity with JavaScript and experience with React. If there are any concepts that need further clarification, let me know and I will try to elaborate. Also, this is as much a journey for me as it will (hopefully) be for many of you, so I am certain to confound some important facts or concepts. Please feel free to kindly correct any of these misunderstandings and I will be happy to edit this or any future pieces.
What is React?
This section wasn’t originally included, but is actually the result of a conversation with my dad. When he heard I was writing an article on React Core, he said, ‘What’s that? You’re writing a reactor? Does the government know about that?’
We laugh but the truth is, we as a community take certain high-level assumptions for granted. So here it is — for all the dads in the world who wonder what their sons and daughters are up to: React is an extremely popular web framework (i.e. used to build web applications) that was open sourced by Facebook. Unfortunately it has nothing to do with nuclear fusion or submarines.
Into the Code
package.json
When I first jump into a library I begin at the package.json
, which is often located in the project’s root directory. The package.json
’s main field points to the project’s entry point and is where your main module’s exports object is returned. In other words, when a module is ‘required in’, this is the point where the API is imported from. If we look for the main field in the package.json
located at React’s root, however, we find the main field is strangely absent. We can gain insight as to why this is the case from the React Codebase overview, here. There are two important things to note from this source. The first is the React repository on GitHub is a monorepo (i.e. React and all its related projects are kept in a single repository), the second is
[t]he npm metadata such as
package.json
files is located in thepackages
top-level folder.
Cool, the packages
directory might be a good place to explore next. Before moving on, however, you may be wondering why package.json
exists at the root, if it doesn’t actually provide an entry point. One subtle clue to this question lies in the package.json
’s dependencies. The only dependencies that the package relies on are devDependencies (in other words, no direct dependencies). Furthermore, the npm scripts in the scripts field are all development scripts, giving access to useful CLI commands like npm run build
, npm t
, npm run flow
, etc. Therefore, it is reasonable to assume this package.json
exists to set up the development environment.
Let’s now move on to the packages
directory at the root. We’re interested in the core React module for now so navigate to react/packages/react/package.json. You should notice that this package.json
contains a main field that points to index.js
as well as a few dependencies. If we navigate to react.js
(credit to Hozefa for his corrections to this paragraph) we might be surprised to find something that looks like this:
That’s right — only three lines of code, one of which is 'use strict';
, the other appears to be exporting a non-existent file in a non-existent directory. Once again, the reasons for this mysterious file are answered by the Codebase Overview:
When we compile React for npm, a script copies all the modules into a single flat directory called
lib
and prepends allrequire()
paths with./
A Brief Note on Haste
Navigating to https://unpkg.com/react@15.4.1/lib/, we can see that there is indeed a file named React.js
. But from where did this file originate? The answer lies in Facebook’s custom module system, Haste. Haste was developed to be convenient for projects with large codebases, so that files can be moved to different locations within a project without having to worry about specifying new relative file paths. The key distinction between Haste and Common.js, however, is that all filenames within a Haste project must be globally unique. When a new file is created in a Facebook project, Facebook requires that file include a license header. Within that header appears a line like * @providesModule React
where the name following @providesModule
should match the name of the newly created file. So, in this case @providesModule React
imports the React module. This is how modules are created in Haste.
React.js
Having globally unique filenames also makes finding files incredibly simple on GitHub and most text editors. Pressing t
anywhere in React’s GitHub repository, we can type react.js to search for the core React module. Indeed, we find a file named React.js
located in src/isomorphic as the third hit from the top.
Navigating to src/isomorphic/React.js we finally see a Javascript file that’s more than three lines of code!
The Core Module
React.js (cont)
Lines 1–10: As I mentioned before, each source file in React requires a license header. These lines contain information about the license and are used by Haste (line 9) to uniquely identify the module.
Lines 14–21: These lines leverage Haste to import React.js
’s module dependencies. The modules required in here will ultimately help comprise React’s top-level public API. We’ll dive into their code in a bit.
Lines 23–24: These lines require in two more modules that do not comprise the top-level API but are used within the React.js file itself. One interesting note about the warning module: if you search the repository for it, you might be surprised to find that it doesn’t exist in the React repository. Instead, it is located in Facebook’s fbjs repository here, which is included as a dependency in React’s package.json
. If you’re curious, you can learn more about why it’s kept in another repository here.
Lines 26–28: The code here serves two purposes in my mind. 1) It creates a series of utility variables that are more expressive and less verbose than continuously accessing properties on the ReactElement object and 2) the newly recreated variables can safely be reassigned in the development environment, without overwriting the modules’ functionalities.
Lines 30–35: One convention used frequently throughout the React source code is the use of __DEV__
pseudo-global variable. You can read more about it here, but the essential concept to understand is that it guards development-only blocks of code — in other words, it creates blocks of code that will only execute in the developer environment and will not execute in production environments. In this case, if React is being executed in the development environment, require in ReactElementValidator and overwrite the variables assigned on lines 26–28 with their ReactElementValidator counterparts.
Line 37: This might seem like an unnecessary line at first, but its purpose emerges on closer inspection. In JavaScript functions are first class (i.e. are just like any other value) and can be assigned to variables. Here, __spread
is simply a variable that holds a reference to the same function as Object.assign
, which can be learned about here. This is important because as we’ll see in a moment, React.__spread
has been deprecated and this ensures that it now points to the same function as Object.assign
. If this single line of code still looks mysterious, try running the following code in a REPL and consider the behavior that might generate the following output:
Lines 39–52: Once again, we encounter an if
block whose conditional checks for the __DEV__
execution environment. Once again, this is React’s way of blocking development code in the production environment. Clearly, the functionality of this block depends heavily on the warning
function required earlier in the file. Therefore, in order to understand the intention of this code, we must first understand warning.js
.
A Detour through warning.js
I’ve decided to include this section for the sake of thoroughness and because its functionality is leveraged liberally throughout the React codebase. However, the module’s title is self-explanatory and the code relatively trivial, so feel free to skip this section if you’d like.
The warning module begins by importing emptyFunction from the emptyFunction.js module. The important line here that is ultimately used by our warning module is line 24, which looks like:
If we strip away the FlowType from this function we’re left with:
This is an idiomatic way of stripping a function of its arguments and then simply returning an anonymous function. This is important because we don’t want to be warned while executing in non-__DEV__
environments. In this case, however, we know that the program’s execution environment is the development environment (i.e. __DEV__
evaluates to true
), so warning
will be overwritten on line 40.
This warning
function’s purpose is to communicate errors in a constructive way while in the __DEV__
environment. It accepts a condition
, format
, and ...args
as its parameters. Following line 40, the warning function will check whether format
— which is the error message — is defined. If it’s not, an exception will be raised stating, ‘`warning(condition, format, …args)` requires a warning message argument’. The truthiness of thecondition
argument is then checked and, if false, invokes printWarning
on line 26. printWarning
will then check if the console exists and, if so, it will invoke console.error
on message; otherwise, it will attemptthrow new Error(message);
. Finally, warning
is exported.
Back to React.js
Now understanding the what, why, and how of warning.js
we can continue stepping through the code. Line 40 of React.js represents the beginning of a pattern common throughout the React codebase where a variable called warned
is set to false
. On the next line, __spread
is reassigned to a function that invokes warning
passing in the warned
flag and the message referred to here as format
. This ensures that in production environments React.__spread
aliases Object.assign
, while in __DEV__
environments invoking React.__spread
warns the user that __spread
is now deprecated and its use should be avoided. Next, the warned
flag is flipped to true
, so that warning
only logs or throws an error the first time React.__spread
is invoked. Finally, Object.assign
is invoked on arguments
and the resulting object is returned. Moral of the story: React.__spread
should be avoided when possible and Object.assign used in its place.
Lines 54–71: This is where React’s top-level public modern API is defined. For example, React.Component
and React.PureComponent
are exposed here as Component: ReactComponent
and PureComponent: ReactPureComponent
, respectively.
Lines 58–64: This is where React.Children
's API is defined, which includes the following methods: map
, forEach
, count
, toArray
, and only
.
Lines 66–67: These lines define the React.Component
and React.PureComponent
API.
Lines 69–71: This is where the React.createElement
, React.cloneElement
, and React.isValidElement
are defined.
Lines 75–90: This is where React’s top-level public classic API is defined. We’re not going to spend much time with these, but some of them are worth exploring on your own.
Lines 75–81: This is where React.PropTypes
, React.createClass
, React.createFactory
, and React.createMixin
are defined and exposed as public API.
Line 85: This line defines React.DOM
, which interestingly is not DOM specific, but actually a series of isomorphic helpers that return DOM strings.
Line 87: Defines React.version
, which contains the current version of React.
Line 90: Defines React.__spread
. Once again, this method is deprecated and should be avoided, in favor of Object.assign
when possible.
Line 93: Finally, the React module is exported on the last line of React.js
.
A Deeper Dive
We’ve now defined React’s public API. At this point the path diverges in a number of directions. We could explore React.PureComponent
, React.Component,
or React.Children
, but I’ve decided to follow the path of least resistance and prioritize our exploration based on ease. Therefore, we’ll begin with the most straight-forward module, React.Children.onlyChild
, followed by the React.Component
and React.PureComponent
modules, and finish by circling back to the more complex constituents of React.Children
.
The React.Children.onlyChild Module
The onlyChild
module is a single function exported by react/src/isomorphic/children/onlyChild.js
. This module requires only two other modules as its dependencies: invariant
and ReactElement
. The invariant
module is very similar in functionality to the warning
module we investigated earlier where if a condition does not evaluate to true, an exception will be raised. The difference being the exception raised by invariant
will be raised in both __DEV__
and production environments alike. The other module, ReactElement
, is located in react/src/isomorphic/classic/element/ReactElement.js
and consists of 386 lines of code. For the purposes of React.Children.onlyChild, however, we’re only interested in lines 372–384. The important thing to understand about ReactElement.isValidElement
is that it accepts a React component and validates that it is indeed a React component by:
It first checks the component’s type to determine if it’s an object and does not equal null and the component has the $$typeof property that’s set to the REACT_ELEMENT_TYPE Symbol. If all conditions are true, ReactElement.isValidElement
returns true. Therefore, if onlyChild
is not passed a single valid React component, it will throw an invariance exception; otherwise, it will return the component.
I like to evangelize testing when I can because I believe a lot can be gained by reading through a module’s tests. Tests guard against regressions and help define the contract by which their covered functionality must abide. Therefore, I’d like to spend a moment on React.Children.onlyChild’s test suite.
A Note on Testing in React
Testing in React is done via Facebook’s Jest testing framework. A file’s tests can be found in an intuitive and predictable location. For each tested file, there will be a colocated directory called __tests__
that will hold the test suites for all files in the same directory. For example, because the onlyChild
module is located in the src/isomorphic/children
directory, its tests will be found in the src/isomorphic/children/__tests__
directory. This makes finding a file’s tests manageable and makes remembering to move a file’s tests simple.
Back to onlyChild
Now that we know where onlyChild
’s tests are located, we can better understand its code by reading them. Go ahead and navigate to src/isomorphic/children/__tests__/onlyChild-test.js
. I won’t spend too much time on the implementation of the tests here, but know that each test passes and sheds light on how and why onlyChild
might be used. Immediately we learn that onlyChild
:
- should fail when passed two children
- should fail when passed nully values
- should fail when key/value objects
- should not fail when passed interpolated single child
- should return the only child
All of that information is gathered in a matter of seconds by simply glimpsing onlyChild
’s validation suite.
React.Component
Next, I’d like to look to pivot and walk through React.Component
. Don’t worry, we’ll return to investigate the remainder of the React.Children
code in a bit. We begin with React.Component
and not React.PureComponent
because React.PureComponent
is built on top of React.Component
, so having an understanding of the former facilitates the latter.
React.Component
can be found in the project directory at react/src/isomorphic/modern/class/ReactComponent.js
. We’ll skip the license headers from here on out because their format is unchanged throughout the repository.
Line 14: Requires the ReactNoopUpdateQueue
module. This module is essentially a dummy replacement for when a ReactComponent’s updater parameter does not exist. Its functionality is minimal and is primarily useful for warning the user against updating unmounted components.
Lines 16–19: These lines require in utility modules from the src/shared/utils
directory as well as from the fbjs repository. Once again, the invariant
and warning
modules are required as well as a module called emptyObject
which comes from the fbjs repository. This module creates an empty object that is immediately frozen with Object.freeze
in the development environment and that object is then exported.
A Detour through canDefineProperty Module
The last module to be required in is the canDefineProperty
module from react/src/shared/utils/canDefineProperty.js
. This is a very interesting module that leverages theObject.defineProperty
method. If you’re unfamiliar with Object.defineProperty, the MDN documentation does an excellent job explaining its purpose:
This method allows precise addition to or modification of a property on an object. Normal property addition through assignment creates properties which show up during property enumeration (
for...in
loop orObject.keys
method), whose values may be changed, and which may be deleted. This method allows these extra details to be changed from their defaults. By default, values added usingObject.defineProperty()
are immutable.
In other words, properties created by Object.defineProperty are both unchangeable and non-enumerable by default. If we look at the canDefineProperty.js in the React repository, we see how this is used.
Lines 15–24: A flag called canDefineProperty
is declared and initialized to the Boolean value of false
. Then, if React is executing in the __DEV__
environment, it will attempt to invoke Object.defineProperty
on a dummy object. You might be wondering why this step is necessary if we’re not doing anything with the dummy object. Well, if we look at the try
’s corresponding catch
statement, we’ll see a comment indicating that Internet Explorer will fail on Object.canDefineProperty
. This means line 19 will raise an exception before the Boolean is flipped and the exception will be swallowed by the catch
statement. Finally, canDefineProperty is exported.
Back to ReactComponent.js
Now that we know what the canDefineProperty module exports, let’s return to ReactComponent.js
.
Lines 24–31: These are the lines that define the base class which is extend to build React components! The function on line 24 is a pseudoclassical constructor function that creates React Component instances. For those of you unfamiliar with the pseudoclassical object instantiation pattern, you can read more about it here. As we can see, a component’s props, context, and updater are set here, based on the parameters passed into the constructor at run time. If updater
is unspecified, it will be set to the ReactNoopUpdateQueue module required above. The component’s refs
are set to the empty object exported here.
When we write, for example:
class button extends React.Component {
// some code
// ..
// ..
}
this is the function that is actually being extended!
Line 33: Sets the isReactComponent property on ReactComponent.prototype and assigns an empty object to it.
Lines 60–69: These lines define the setState
method on the ReactComponent.prototype
. The first thing that setState
does is to guard against the invariance where the partialState
parameter is neither of type ‘object’ nor ‘function’ nor null
. Recall that partialState can not only represent what new information should be merged into state, but how it can be merged as well. Sophia Shomaker has a great primer on passing callbacks to setState
here. If partialState
is none of those types, it will raise the invariance; otherwise the intention to set a new state will be passed along to the React reconciler via the enqueueSetState
method on the updater.
Lines 85–87: These lines define the forceUpdate
method on the ReactComponent.prototype
. This function merely tells the reconciler that something has changed and the state should be updated by invoking the enqueueForceUpdate
method on the component’s updater
property. Recall that the updater is injected at runtime and the default updater property might not possess enqueueForceUpdate
!
Lines 94–127: These lines are used in the development environment to warn developers against utilizing now-deprecated APIs. Specifically, if a developer uses the isMounted
(line 96) or the replaceState
(line 101) APIs from the deprecatedAPIs
object, a warning
will be raised. The defineDeprecationWarning
on line 107, is why the canDefineProperty
module required earlier is necessary. It adds deprecated API method names to the ReactComponent.prototype
as non-enumerable properties, so when they’re accessed a warning
will be raised. Recall, when properties are added to objects with Object.defineProperty
, they’re essentially invisible until they’re accessed. Here the deprecated APIs are being ‘hidden’. If they are accessed, however, the warning on their getters will be raised. Finally, the for…in
loop on line 122 programmatically adds deprecated APIs to the ReactComponent.prototype
. Interestingly, APIs deprecated in the future can simply be added to the deprecatedAPIs
object on line 95, and the appropriate warning will be raised when accessed in the __DEV__
environment.
Line 129: The ReactComponent module is exported.
That’s it for the ReactComponent module’s API. Now that we’ve covered the ReactComponent module, we can investigate ReactPureComponent
, which inherits from it.
ReactPureComponent
A close reading of ReactPureComponent.js
which can be found in the react/src/isomorphic/modern/class
directory, exposes stark similarities between it and its ReactComponent.js
counterpart. In fact, the ReactComponent module is imported into the ReactPureComponent module on line 14, along with the ReactNoopUpdateQueue and emptyObject modules — both of which are module dependencies to ReactComponent
. We’ll skip these lines, because their functionalities can be referenced above.
Lines 22–30: This function should look very familiar. It is the exact same function that appears in on line 24 of ReactComponent.js
, only called ReactPureComponent
. Indeed, line 23 is a comment admitting that the code was duplicated from the ReactComponent module.
Lines 32–37: These lines might seem unfamiliar at first, especially if you’re unfamiliar with delegate prototypes in Javascript. If you would like to know more about inheritance and the prototypal chain, MDN has an excellent article that you can read here. There is a small amount of indirection here with the ComponentDummy pseudoclass that I will do my best to break down. The ComponentDummy constructor is defined on line 32 and will instantiate an empty object that delegates to the ComponentDummy.prototype
. Next, ComponentDummy
’s prototype is overwritten by ReactComponent
’s protoype on line 33. Then, ReactPureComponent
’s prototype is overwritten by an instance of ComponentDummy, which is just an empty object whose __proto__
points to ReactComponent
’s prototype on line 34. This allows the behavior defined on the ReactComponent.prototype
to delegate to the ReactPureComponent.prototype
without having any knowledge of the existence of ReactPureComponent
. If that doesn’t make sense, take a look at the following snippet and try to understand what’s going on in it.
Next, ReactPureComponent is reassigned to the constructor property on the prototype on line 35. This is necessary because the constructor was overwritten by the new ComponentDummy instance earlier. Line 37 may look a little odd at first, considering it seems like the constructor we just set on the ReactPureComponent.prototype
is being overwritten. Fortunately it’s not. The reason for this is Object.assign
's behavior. It
only copies enumerable and own properties
While the constructor property on the ReactPureComponent.prototype
is enumerable because we defined it literally, the constructor on ReactComponent.prototype
is not! Finally, we assign a property to the ReactPureComponent.prototype
called isPureReactComponent
that holds the Boolean value of true. Lastly, we export the ReactPureComponent
.
Before moving on, take a look at ReactPureComponent
’s tests colocated at react/src/isomorphic/modern/class/__tests__/ReactPureComponent-test.js. Looking at the first test you immediately see that our ReactPureComponent ‘should render’. I want to take a moment to step through this code, because I believe it’s valuable to understand how the test actually leverages ReactPureComponent
. On line 24, the variable renders
is declared and initialized to the value 0
. This variable will be a counter that holds the number of times our component is rendered. On the next line, Component
extends React.PureComponent
and its behavior are defined. On line 28, the component’s state is set to { type: 'mushrooms' }
. On lines 30–32 the component’s render
method is defined. When this render
method is invoked, two things happen: 1) the renders
variable on line 24 increments as a side-effect and 2) the interpolated this.props.text[0]
is wrapped in a div
tag. On line 36, a container
variable is declared and initialized to an empty HTML div
element. On line 45 the text
variable is initialized to the array literal ['porcini']. On the next line the Component
is rendered to the empty div
element and the text
is passed in as the component’s props. On line 46, container.textContent
is asserted to be the string ‘porcini’. Furthermore, because the component is rendered, the renders
variable is asserted to have been incremented 1. Lines 45–48 demonstrate similar behavior. Scrutinizing lines 50–53, clarifies a behavior that can be confusing to beginners of React. Because React component’s only do a shallow comparison of its props, Component
will not be rerendered. In other words, because the array that wraps the string is the same — even though the string itself has been updated — the Component
will not be rerendered. This is why container.textContent
is still asserted to be the string ‘morel’ and the value of renders is expected to be 2. Similarly, on lines 56–58 the setState
method is invoked on the Component
instance passing in the same state as before. When a partial state that is already present in state is attempted to be merged into state, the component does not rerender. This explains why both container.textContent
and renders
are unchanged. Finally, in lines 61–63 a new partial state is merge, causing state to update so the component rerenders. Now container.textContent
is expected to be the string ‘portebello’ which was set on line 50 and renders
is expected to be 3. We also learn that the ReactPureComponent
‘should override shouldComponentUpdate.’ Basically this means that when shouldComponentUpdate returns true, the component’s normal behavior will be overridden by shouldComponentUpdate and the component will always rerender when told to. Lastly, we expect that ReactPureComponent
‘extends React.Component’. Indeed, we see on lines 87–88 that this
which is our instance of ReactPureComponent is both an instance of ReactPureComponent
and ReactComponent
.
ReactElement.js
You’ve just walked through the interface used to build the React components that will ultimately represent the browser’s DOM. You might be wondering, how those classes and their instances are translated into nodes on the so-called virtual DOM. If you’ve never seen a React Element before, they look like this:
which represents:
<div>
<span></span>
</div>
in the traditional DOM. The short answer is these React Elements are created in the ReactElement module. ReactElement.js
is located in the react/src/isomorphic/classic/element directory and is exposed in the React API as React.createElement
, React.createFactory
, and React.cloneElement
.
Line 14: This line imports the ReactCurrentOwner module which is simply an object whose only property, current
, holds the value null
, which can be of the FlowTypes null
, ReactInstance
, or Fiber
. If you’re unfamiliar with FlowTypes, you can read more about their API here.
Lines 16–18: These lines require in the familiar modules, warning and canDefineProperty. If you need to be reminded of their functionality, look above. The hasOwnProperty
variable is set to Object.prototype.hasOwnProperty
and prevents making a prototype jump to the Object.prototype
.
Line 20: This line requires in the REACT_ELEMENT_TYPE module. If you navigate to this file, its code might appear complicated at first. All that it’s really doing, however is checking the environment for the presence of the Symbol factory function and then invoking the Symbol constructor on the string ‘react.element’ returns a symbol object. Its code is found here.
If you’re wondering what Symbol.for(‘react.element’) does here, it’s checking for the presence of the symbol with the key ‘react.element’. If it finds it, it returns that instance of symbol. Otherwise it creates a new symbol instance with the ‘react.element’ key and returns it. More can be read here.
Lines 22–27: These lines declare the variable RESERVED_PROPS
and assign to it an object literal with the properties: key
, ref
, __self
, and __source
all set to the Boolean value true
. As we’ll soon see, these properties on the RESERVED_PROPS
object refer to properties that cannot be overwritten or manually assigned on React Elements.
Lines 31–41: On the first line, hasValidRef
checks whether the execution environment. If it is set to __DEV__
, the function then checks if the ref
property is defined on the config
parameter. On line 34, the variable getter
is assigned to Object.getOwnPropertyDescriptor(config, 'ref').get
. If you are not familiar with getOwnPropertyDescriptor
, you can read about it here. Essentially, it returns an object whose properties describe various non-obvious aspects of the property passed to it. One of these properties is the get property, which is why the line ends with .get
. Therefore, this line is looking up the property’s getter and assigning it to the getter
variable. Next, if the getter
variable is defined and the getter.isReactWarning
flag is set to true, which you’ll see in a moment, hasValidRef
returns false. In other words, if the ref
property on the config object is not valid. Lastly, if not in the __DEV__
environment or in the __DEV__
environment but didn’t early return, the hasValidRef
returns a Boolean value that returns true
if config.ref
is not undefined
.
Lines 43–53: The hasValidKey
function behaves very similarly to its hasValidRef
function. The only difference is that it checks for the key
property rather than ref
.
Lines 55–74: The defineKeyPropWarningGetter
begins by defining a new function called warnAboutAccessingKey
. The purpose of this function is to check whether the variable specialPropKeyWarningShown
in the parent scope remains undefined
. If it is, it will set the specialPropKeyWarningShow
flag to the Boolean value of true
and then raise a warning stating that ‘key is not a prop…’. The flag is necessary to avoid raising multiple warnings for the same event. Next, the property isReactWarning
is defined on the function and set to the Boolean value false
. Finally, key is added as a non-enumerable property to props whose getter function is warnAboutAccessingKey
function that was just defined. Basically, this function is reminding you that key is not a property on the props object. If it is accessed in production environments, it will be undefined
. In __DEV__
environments, however, it will raise this warning.
Lines 76–95: The function defineRefPropWarningGetter
defined here is very similar in functionality to defineKeyPropWarningGetter
. The difference is that it adds a non-enumerable warning getter to the ref
property on the props
object.
Lines 117–177: These lines define the function ReactElement
. This will ultimately be exported and used in many of the methods on the top-level API. The function begins by defining an object and assigning it to the element
variable. This object holds the $$typeof
property which is what React uses internally to determine if an object is a bona fide React component. Next, the built-in properties that component contains are added to the object: type
, key
, ref
, and props
. Lastly, the _owner
property is defined on the ReactElement instance, which contains a reference to its parent (creating) component. You might be wondering why these properties are defined literally, rather than programatically. The reason is discussed in depth here, but basically defining the properties this way is an optimization. Next, the ReactElement
function checks if it’s executing in the __DEV__
environment. It it is, React will create a property _store
on element that is initialized to an empty object literal. Line 143 ensures that Object.defineProperty
exists in the environment. If it does, a series of non-enumerable properties are defined on the element.store
object using Object.defineProperty
. Lastly, if Object.freeze
exists in the execution environment, the element
object is frozen and the resulting object is returned.
Lines 183–265: These lines define the ReactElement.createElement
function. If that name sounds familiar, it’s because React components written in the JSX language transpile to React.createElement
. This method is actually stored on the ReactElement
function.
Lines 184–192: These lines declare the variables that comprise the Reserved namespace in React component’s. They are each initialized to null
.
Line 194: This line checks if the config
parameter is not a nully value.
Lines 195–200: If config
is not null
, it executes the hasValidRef
and hasValidKey
functions defined above on the config parameter. If they exist and are valid, the config.ref
and config.key
are assigned to their respective variables.
Line 202: If config.__self
is undefined
, assign null to the self
variable. Otherwise, assign self
the value stored at config.__self
.
Line 203: Same as line 202, but with source
variable and property.
Lines 205–210: These lines simply add the remaining enumerable properties on the config
object to the props object. It is important to note here, however, that properties on the config object cannot overwrite properties defined on the RESERVED_PROPS
object because of line 208.
Line 215: This line takes measure of the number of children
arguments that are passed into the React.CreateElement
function. It accomplishes this by subtracting the first two parameters from the total length of the arguments
array-like object. For example, if the number of children arguments is (arbitrarily) 4, the total number of arguments will be 6 (4 + 2).
Line 216–218: If the number of children arguments is equal to 1, the children
parameter is assigned to props.children
.
Lines 218–229: If the number of children arguments is greater than one, they will must be stored in an array. A new array is initialized and assigned to the childArray
variable. The for
loop iterates through the arguments
array-like object and pushes the children
parameters to the childArray
. Next, if the function executes in the __DEV__
environment, the childArray
array is frozen. Lastly, the childArray
variable is assigned to props.children
.
Lines 232–239: These lines check to see if the type
parameter is defined and if that parameter has the defaultProps
property. If both conditions are true
, the variable defaultProps
is initialized to type.defaultProps
. Next, a for... in
loop iterates through the properties on type.defaultProps
, and if the property’s value is not undefined
, the property and its value are copied into the props object.
Lines 240–255: Next, if the function is executing in the __DEV__
environment, React checks iff key
and ref
are defined. If they are, another if
statement checks if the type of props.$$typeof
is equal to the string ‘undefined’ or not equal to REACT_ELEMENT_TYPE
. If both are true, a displayName
variable is initialized which will be passed into the defineKeyPropWarningGetter
and defineRefPropWarningGetter
functions defined earlier.
Lines 256–264: Lastly, ReactElement is invoked on the variables defined above and the resulting object returned!
Lines 271–280: In these lines another piece of the top-level API functionality is defined, ReactElement.CreateFactory
. On line 272, it defines a function called factory, binding the functions this
value to null
, as well as binding the type
parameter. Then, on line 278 the type
parameter is exposed directly on the function as a property so that it can be easily accessed on elements. Finally the factory
function is returned.
Lines 282–294: The function defined in these lines is pretty straight-forward. It accepts an existing element and a new key as its parameters. It then invokes ReactElement
on properties taken from oldElement
(the existing element), also passing in the newKey
parameter, and returns the newElement
object.
Lines 300–369: If these lines look familiar it’s because they very closely mirror the functionality defined in React.CreateElement
. The primary difference is that ReactElement.CloneElement accepts an element rather than a type as its first argument and essentially creates a new element from its input.
Line 304: In this line element.props
is copied into the newly declared variable props
. The reason Object.assign
must be used is because objects are passed by reference. If we simply assigned props
to be element.props
it would hold the exact same object. Therefore, any changes in one would be reflected in the other. Considering the purpose of ReactElement.CreateElement is to clone a new element from a pre-existing one, this behavior is undesirable. This is why a new object is created and all of its properties are copied over from the element.props
.
Lines 307–317: Once again reserved names are extracted and explicitly defined.
Lines 319–345: So far the new element’s variables have all been copied over from the source element. In these lines, however, ReactElement.CloneElement
checks if its config
parameter does not evaluate to a nully value. If it doesn't, hasValidRef
and hasValidKey
are invoked on the config
object. If the result of that invocation is true
, the variables ref
, key
, and owner
are overwritten. In the remaining lines, the remainder of the props that are neither undefined
nor properties on the RESERVED_PROPS
object are copied over to the new element.
Lines 349–358: In the same way the children
arguments are copied over to the new element above, the children
arguments are copied from the source to the target element here.
Lines 360–368: Finally, ReactElement
is invoked on the newly defined variables in these lines and the resulting object returned.
Lines 378–384: These lines were already discussed. For an explanation of ReactElement.isValidElement
's functionality, see above.
ReactChildren.js
Finally, we arrive at the last piece of the React Core API. This part has been saved for last because it is the most confusing and requires a relatively deep understanding of the Javascript language and patterns. I will try to explain these concepts to the best of my abilities.
The main module is located in the React repository at react/src/isomorphic/children/ReactChildren.js
.
Lines 14–18: These lines import the ReactChildren module’s dependencies. The ReactElement and emptyFunction modules should look familiar at this point, but we’ll investigate the PooledClass and traverseAllChildren modules as they are encountered.
Lines 20–21: If you peek into the PooledClass module, you’ll notice a number of methods on the exported object. Each of those methods achieves approximately the same goal but have different arities. In lines 20–21, we declare and initialize twoArgumentPooler
and fourArgumentPooler
to be PooledClass.twoArgumentPooler
and PooledClass.fourArgumentPooler
from the PooledClass module, respectively.
Lines 24–27: These lines accept a user-provided key and match for escape characters and replace them with the string ‘$&/’.
Lines 38–42: These lines define a class constructor function that accepts two parameters: forEachFunction
, which will wind up representing the callback function passed into ReactChildren.forEach
, and forEachContext
, which will be discussed when we investigate the traverseAllChildren module. The parameters are then passed to the instance’s func
and context
properties while the count
property is assigned to the number 0.
Lines 43–47: These lines define a destructor
method on the ForEachBookKeeping
function. This method effectively resets the properties on the instance created by the class constructor by reassigning the instance’s values to null
and 0.
Line 48: This line leverages the functionality of the PooledClass module’s addPoolingTo
method. In order to better understand this function’s purpose and behavior, let’s take a detour through the PooledClass module.
Detour Part I: The PooledClass Module
Lines 94–111: If you don’t have any experience with Flowtype, the code in these lines might look foreign. The <T>
on line 95 is a generic type declaration that will replace subsequent T
s. The addPoolingTo
function accepts two parameters: CopyConstructor
, which should be a generic Class
constructor function, and a pooler
parameter, which should be of the Pooler
type which can be of type any
, as declared on line 83. Lines 97–100 declare the type of the function’s return value, which should be of of type Class
constructor with the getPooled
and release
methods, that return the generic type T
and void
(undefined
), respectively. Lines 101–110 actually define the body of the addPoolingTo
function. On line 103, the variable newKlass
is assigned to the parameter CopyConstructor
. Next, the instancePool
property is added to newKlass and initialized to an empty array literal. Next, the getPooled
property is added to NewKlass
and set to the pooler
parameter. Lastly, the release property is added to the NewKlass
constructor and is set to the standardReleaser
function defined above. The NewKlass
constructor is then returned. Essentially, PooledClass.addPoolingTo
extends a class constructor with a pooler. In the case of React.Children.forEach, the CopyConstructor
parameter being passed in is the ForEachBookKeeping
function, while the pooler
parameter is the twoArgumentPooler
function.
Lines 35–44: The twoArgumentPooler
function accepts two arguments, a1
and a2
. So far we’ve seen the this
keyword used to build class instances in the pseudoclassical instantiation pattern. On the next line, however, we see the this
keyword used in a different context. Here, this
refers to the object left of the calldot — in this case, ForEachBookKeeping
. Recall that the NewKlass
parameter equals ForEachBookKeeping
and the pooler
parameter equals twoArgumentPooler
when invoked like ForEachBookKeeping.getPooled
. Lines 37–41 check whether Klass.instancePool.length
is not equal to 0, and if not, pops the last element from the Klass.instancePool
array and assigns that element to the newly declared variable instance
. It then invokes the Klass
function on the a1
and a2
arguments, setting the value of this to the newly popped instance
object. If the Klass.instancePool array is empty, on the other hand, the twoArgumentPooler leverages the new
keyword to invoke Klass
on a1
and a2
and returns the new Klass instance.
Back to ReactChildren
Lines 50–53: The function defined in these lines is a little difficult to understand without the appropriate context. Nevertheless, I will do my best to describe its intention and the meaning underlying its parameters and syntax. Perhaps the most challenging mental hurdle that you must overcome here is that forEachSingleChild
is the callback passed to the traverseAllChildren
function that will be investigated in a moment. The bookKeeping
parameter refers to the object returned by the ForEachBookKeeping
class constructor, which as a reminder contains three properties: func
, context
, and count
. The child
parameter refers to the currently-exposed child component. Lastly, the name
parameter is exposed for consistency but is not actually used in the function. The line destructures the func
and context
variables to equal bookKeeping.func
and bookKeeping.context
, respectively. Lastly, func
is invoked on the child
parameter, the bookKeeping.count
value is incremented, and func
’s this
value is set to context
parameter.
Lines 67–75: The forEachChildren
function constitutes part of React Core’s top-level public API and is exposed as React.Children.forEach
. The function accepts three parameters: children
, which is a collection of child components, forEachFunc
, which is the function to be invoked for each leaf child element, and forEachContext
, an optional argument that becomes the forEachFunc
’s (callback) value of this
.
Let’s dissect this function further.
Lines 68–70: According to React’s Top-Level API documentation, forEach returns a nully value if children is a nully value. In the lines here, React checks for a nully value, and if it finds one, returns that nully value.
Lines 71–72: These lines invoke ForEachBookKeeping.getPooled
, passing in forEachFunc
and forEachContext
as parameters. Remember, ForEachBookKeeping.getPooled
is merely an alias for the twoArgumentPooler
whose this
value is bound to ForEachBookKeeping
! Because ForEachBookKeeping.instancePool.length
equals 0, a new instance of ForEachBookKeeping is assigned to the traverseContext variable on line 71.
Line 73: This line marks our first introduction to the traverseAllChildren module.
Detour Part 2: The traverseAllChildren Module
The traverseAllChildren module recursively invokes a callback on each leaf child element in the component tree. It’s also important to note that it returns a number equal to the total number of leaf nodes in the component tree. The module can be found at react/src/shared/utils/traverseAllChildren.js. Instead of starting at the top and working our way down, as we have been, let’s begin at the function at the bottom traverseAllChildren
.
Lines 213–219: These lines define the function that constitutes the traverseAllChildren
API. The function accepts three parameters, children
, the collection of child component elements and their sub-children components, callback, which in this case is actually the forEachSingleChild
function, and the optional third parameter traverseContext
. Once again, traverseAllChildren checks if children
is equal to null or undefined and returns the number 0 if it is. On line 218, the function traverseAllChildrenImpl is invoked passing in children
, the empty string ‘’, the forEachSingleChild
callback, and the traverseContext
.
Lines 45–54: The function defined in these lines is intended to identify a component within a set. getComponentKey
accepts a component and its index as its only parameters. It does some typechecking on line 48, namely iff the component is defined, the component’s type is equal to ‘object’, and the key property on the component is not a nully value, the key is escaped so it is safe to use as a reactid. Otherwise, the component’s index in its collection is used as its reactid. The latter option is unfavorable, but works well enough.
Lines 64–195: This is the function that actually recursively traverses the children component tree. The function accepts four parameters: children
, the child component tree, nameSoFar
, which starts out as the empty string ‘’, callback, in this case the forEachSingleChild
function and is the function that is invoked for each leaf node, and the traverseContext
, callback
’s this
binding.
Line 70: This line declares the variable type
and initializes it to the children
parameter’s type.
Line 72–75: These lines check the type variable for ‘undefined’ or ‘boolean’, and if either is strictly true
the children
variable is assigned to null
.
Lines 77–91: These lines check if the value of children
is null, the type
variable holds either ‘string’ or ‘number’, or type
is an object whose $$typeof
property is the REACT_ELEMENT_TYPE
variable. In other words they check if the current location has reached the so-called base case (i.e. reached a child leaf element). If any of these conditions are true, the callback parameter is invoked on traverseContext
, children
, and nameSoFar
, respectively. It’s worth noting here that callback
’s arity perfectly matches that of forEachSingleChild
’s in the ReactChildren module. Finally, because this point must be a leaf in the component tree the number 1 is returned which will later increment the subTreeCount
value.
Lines 98–108: If children is not one of the atomic types listed above nor the value null
, it must be a collection of children. Line 98 checks if the children collection is an array. If children is an array, line 99 iterates through each of its elements. Line 100 names sets the utility variable child
to be the current element of children at index i
. The return value of getComponentKey is appended to nextNamePrefix and assigned to the variable nextName
. Lastly, traverseAllChildrenImpl
is recursively invoked on the child element. The return value of this call will be a number whose value will be added to the current value of the subTreeCount
variable.
Line 110: I won’t go into the how, but I will briefly touch on the why of this line. If the children collection is not an array, React will check if the collection has implemented the iterator protocol. The iterator protocol is a relatively recent addition to the Javascript language. According to MDN,
[t]he iterator protocol defines a standard way to produce a sequence of values (either finite or infinite).
An object is an iterator when it implements a
next()
method…
The return value of invoking the next method is an object that has two properties value
and done
, where done is a boolean whose value is ‘true
if the iterator is past the end of the iterated sequence’ and ‘false
if the iterator was able to produce the next value in the sequence.’ The value
can hold any JavaScript value returned by the iterator. In this case, if an iterator function exists, it is stored in the iteratorFn
variable.
Lines 111–125: These lines begin by checking if the value held in the iteratorFn
variable exists. If it does, the function is partially applied, binding its this
value to the children
parameter. Line 114 checks that iteratorFn
is not an alias for children.entries
. If it is not, the step
variable is declared. This variable will hold the object returned by the iterator.next
invocation. On lines 116 the while
loop will continue looping until the object returned by iterator.next
's done
property is false
. On the next line the return value of getComponentKey
is appended to nextNamePrefix
and assigned to the nextName
variable. Lastly, the traverseAllChildren
function is recursively invoked for each loop of the while
loop in the same manner above.
Lines 126–162: If, on the other hand the iteratorFn
is equal to children.entries
, the iteratorFn
is assumed to be a JavaScript Map. If in the __DEV__
environment, React will throw a warning, saying ‘Using Maps as children is not yet fully supported…’ The didWarnAboutMaps
flag is then flipped to true so the error won’t be thrown again. The remaining while loop in this block of code is the same as lines 111–125 above.
Lines 163–192: These lines begin by checking if the type of children is an object. If it is, the variable addendum
is declared and initialized to the empty string. Next, React checks if it’s executing in the __DEV__
environment, and if it is, runs a series of checks that will overwrite the addendum
variable. Then, line 182 invokes the String
constructor on the children
parameter. Lastly an invariant is thrown stating, ‘Objects are not valid as a React child…’
Line 194: This line returns the value held by the subTreeCount
variable which, once again, is the number of leaf child components in the component tree. It will accumulate the total number of leaf nodes as it is returned through the call stack.
Back to the ReactChildren Module
Returning to the ReactChildren module, we arrive at the MapBookKeeping
function.
Lines 87–93: The MapBookKeeping
is very similar to the ForEachBookKeeping
. Both are class constructors and are passed arguments that represent a callback and context. The difference here, however, is that MapBookKeeping also requires the mapResult
and keyPrefix
arguments. The mapResult
will ultimately be initialized to an empty array that will hold the mapped child components while the keyPrefix
argument will be initialized to the empty string ‘’ and will ultimately prefix the React keys.
Lines 94–100: The destructor
method on the MapBookKeeping.prototype
is invoked following the traverseAllChildren
call and is very similar in functionality to ForEachBookKeeping.prototype.destructor
.
Line 101: This line enacts the same functionality as the call to PooledClass.addPoolingTo above. The only difference, however, is that it extends the MapBookKeeping
class constructor with the fourArgumentPooler
rather than its twoArgumentPooler
counterpart. The reason this function is extended with fourArgumentPooler rather than its counterpart is for the simple reason that MapBookKeeping accepts four arguments rather than two.
Let’s now jump down to the mapChildren function, and walk through a call to React.Children.map!
Lines 161–168: The function described here constitutes part of React Core’s top-level API and is exposed as React.Children.map. This function accepts three arguments: children
, the collection of child components usually someInstance.props.children, func
, the projection function invoked on each leaf child, and context
which binds func
’s this
value. The first thing it does is checks if the children parameter is a nully value. If it is, mapChildren
will early-return the children
value. On line 165, a result variable is declared and initialized to an empty array. Because arrays are passed by reference, it can be passed along as React traverses the component tree and accumulate the mapped values. This is why the array is returned on line 167. Let’s now look at mapIntoWithKeyPrefixInternal
.
Lines 133–146: These lines define the mapIntoWithKeyPrefixInternal
functionality. The function’s arity is five, consisting of: children
, the collection of child components passed into mapChildren, array
, which is the result
accumulator array, prefix
, which is initially null
but will hold a string value on subsequent recursive calls, func
, the projection callback passed into mapChildren
, and context
, func
’s this
binding also passed into mapChildren
. It then checks if the prefix argument is null
and if so, escapes it by calling escapeUserProvidedKey
. Otherwise, escapedPrefix
will be the empty string. Next, MapBookKeeping.getPooled
is invoked in the same way ForEachBookKeeping.getPooled
is invoked in the code above; fourArgumentPooler
is invoked with its this value bound to MapBookKeeping
. This call then returns a new instance of MapBookKeeping
. Next, traverseAllChildren is called and works in the same manner as when it is called in forEachChildren
, only passing in the mapSingleChildIntoContext
, which we’ll investigate now.
Lines 103–131: This brings us to the last function of this part of the series. It is the function that is called at each leaf child of the collection of children — we can show this by seeing that its arity matches that of traverseAllChildren
’s callback
parameter and that it occupies the second position of the traverseAllChildren call on line 144. One interesting thing to note about this function is that it is used for the side-effect it creates — notice its lack of explicit return. The first thing this function does is to destructure the result
, keyPrefix
, func
, and context
properties from our bookKeeping instance (an instance of MapBookKeeping). We then invoke our projection (mapping) function, func
, binding the function’s this value to context
, and passing in the child
leaf element and the incremented bookKeeping.count
value. Next, the mapped child is checked to see if it’s an array. We’ll investigate what happens when React finds an array as it traverses the tree in a moment when we look at the toArray function. If the value held by mappedChild is not an array, however, and is not a nully value and is a valid element, the component is cloned, given a new key, and pushed to the result array that gets returned by mapChildren.
Lines 185–187: The function defined here is extremely straight-forward. It simply counts the number of child leaf elements in a component tree. Notice that its callback on 172 always returns null, irrespective of its inputs. Also, notice that it’s the only function in the ReactChildren module that does some work with traverseAllChildren’s return value, which returns the number of leaf child elements. This demonstrates traverseAllChildren
’s suprising composability!
Lines 196–205: This is the last function that we’ll investigate in this part of the series. It accepts a collection of children and returns an array with appropriately re-keyed children. The first thing this function does is creates a new array that will hold the flattened tree. Notice how mapIntoWithKeyPrefixInternal’s arity matches that of its invocation found on lines 108–113. If that function call’s return type is an array, that means the mapped array is a sub-tree, which must be traversed itself, so mapIntoWithKeyPrefixInternal
is invoked on the child component. Interestingly, the new function passed as the projection function on line 112, is essentially an identity function, meaning the new children components are ‘mapped’ to themselves. Essentially, if it discovers an array as it’s traversing through the component tree, recursively traverse that sub-tree pushing its components along the way.
Lines 208–214: These lines expose the API for the ReactChildren
module. For example, the forEachChildren
function is exposed as React.Children.forEach
.
That’s it. We made it…