Understanding The React Source Code (Republished)

Component Mount (Straight, Upper Half)

UI updating, in its essential, is data change. React offers a straightforward and intuitive approach to front-end program with all the moving parts converged in the form of states. Code review is also made easier to me as I like to start with data structures for a rough expectation of the functionalities and processing logic. From time to time, I was curious about how React works internally, hence this article.

It never hurts to have a deeper understanding down the stack, as it gives me more freedom when I need a new feature, more confidence when I want to contribute and more comfort when I upgrade.

This section walks through one of the critical paths of React by rendering a simple component, i.e., a <h1>. Other topics (e.g., Composite components rendering, state driven UI updating and components life cycle) will be discussed in the following articles in a similar, actionable manner.

Files used in this section:
isomorphic/React.js: entry point of ReactElement.createElement()

isomorphic/classic/element/ReactElement.js: workhorse of ReactElement.createElement()

renderers/dom/ReactDOM.js: entry point of ReactDOM.render()

renderers/dom/client/ReactMount.js: workhorse of ReactDom.render(); defines mountComponentIntoNode(), entry point of the logic process, and ReactDOMContainerInfo which is used in DOM rendering

renderers/shared/stack/reconciler/instantiateReactComponent.js: create different types of ReactComponents based on element type

renderers/shared/stack/reconciler/ReactReconciler.js: call mountComponent of various ReactXXXComponent

renderers/shared/stack/reconciler/ReactCompositeComponent.js: ReactComponents wrapper of root element; call mountComponent to initiate TopLevelWrapper, and performInitialMount to initiate ReactDOMComponent

renderers/dom/shared/ReactDOMComponent.js: define ReactDOMComponent

Tags used in the call stack:
- function call
= alias
~ indirect function call

As the locations of source code files can not be obviously derived from import statement in the flat module tree, I will use @ to help locating them in the code snippet listings.

This article is based on React 15.6.2.

From JSX to React.createElement()

I was not consciously aware of using React.createElement(), as the existence of it is masked by JSX from a developer’s point of view.

In compiling time, components defined in JSX is translated by Babel to React.createElement() called with appropriate parameters. For instance, the default App.js shipped with create-react-app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}

export default App;

is compiled to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
render() {
return React.createElement(
'div',
{ className: 'App' },
React.createElement(
'header',
{ className: 'App-header' },
React.createElement('img', { src: logo, className: 'App-logo', alt: 'logo' }),
React.createElement(
'h1',
{ className: 'App-title' },
'Welcome to React'
)
),
React.createElement(
'p',
{ className: 'App-intro' },
'To get started, edit ',
React.createElement(
'code',
null,
'src/App.js'
),
' and save to reload.'
)
);
}
}

export default App;

which is the real code executed by a browser. The code above shows a definition of a composite component App, in which JSX, a syntax of interweaving HTML tag in JavaScript code (e.g., <div className=”App”></div>), is translated to React.createElement() calls.

On the application level, this component will be rendered:

1
2
3
4
ReactDOM.render(
<App />,
document.getElementById('root')
);

normally by a JS entry module named “index.js”.

This nested components tree is a bit too complicated to be an ideal start point, so we forget it now and instead look at something easier - that renders a simple HTML element.

1
2
3
4
5
6
...
ReactDOM.render(
<h1 style={{"color":"blue"}}>hello world</h1>,
document.getElementById('root')
);
...

the babeled version of the above code is:

1
2
3
4
5
6
7
...
ReactDOM.render(React.createElement(
'h1',
{ style: { "color": "blue" } },
'hello world'
), document.getElementById('root'));
...

The first step does not do much really. It simply constructs an ReactElement instance populated with whatever passed down to the call stack. The outcome data structure is:

Figure 1.

The call stack is:

1
2
3
React.createElement
|=ReactElement.createElement(type, config, children)
|-ReactElement(type,..., props)

React.createElement(type, config, children) is merely an alias of ReactElement.createElement();

1
2
3
4
5
6
7
8
9
10
11
12
...
var createElement = ReactElement.createElement;
...
var React = {
...
createElement: createElement,
...
};

module.exports = React;

React@isomorphic/React.js

ReactElement.createElement(type, config, children) 1) copies the elements in config to props, 2) copies the children to props.children and 3) copies the type.defaultProps to props;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
...
// 1)
if (config != null) {
...extracting not interesting properties from config...
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}

// 2)
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children; // scr: one child is stored as object
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2]; // scr: multiple children are stored as array
}

props.children = childArray;
}

// 3)
// Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
...

ReactElement.createElement@isomorphic/classic/element/ReactElement.js

Then ReactElement(type ,... ,props) copies the type and props as they are to ReactElement and returns the instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
var ReactElement = function(type, key, ref, self, source, owner, props) {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,

// Built-in properties that belong on the element
type: // scr:------------------> 'h1'
key: // scr:------------------> not of interest for now
ref: // scr:------------------> not of interest for now
props: {
children: // scr:------------------> 'hello world'
...other props: // scr:------------------> style: { "color": "blue" }
},

// Record the component responsible for creating this element.
_owner: owner, // scr: --------------> null
};
...

ReactElement@isomorphic/classic/element/ReactElement.js

The fields populated in the newly constructed ReactElement will be used directly by ReactMount.instantiateReactComponent(), which will be explained in detail very soon. Note that the next step will also create a ReactElement object with ReactElement.createElement(), so I will call the ReactElement object of this phase ReactElement[1].

Render it

_renderSubtreeIntoContainer() - attach TopLevelWrapper to the ReactElement[1]

The purpose of the next step is to wrap the ReactElement[1] with another ReactElement (we call the instance a [2]) and set the ReactElement.type with TopLevelWrapper. The name TopLevelWrapper explains what it does - wrap the top level element of the component tree passed through render():

Figure 2.

An important definition here is that of TopLevelWrapper, I type three stars here *** for you to CTL-f, as you might need to come back to this definition later.

1
2
3
4
5
6
7
8
9
10
11
12
...
var TopLevelWrapper = function() {
this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
TopLevelWrapper.prototype.render = function() {
return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
...

TopLevelWrapper@renderers/dom/client/ReactMount.js

Please note that the entity assigned to ReactElement.type is a type (TopLevelWrapper) which will be instantiated in the following rendering steps. (Then ReactElement[1] will be extracted from this.props.child through render().)

The call stack to construct the designated object is as following:

1
2
3
4
5
6
7
8
ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer(
parentComponent, // scr:-----------------> null
nextElement, // scr:-----------------> ReactElement[1]
container, // scr:-----------------> document.getElementById('root')
callback' // scr:-----------------> undefined
)

For initial rendering, ReactMount._renderSubtreeIntoContainer() is simpler than it seems to be, in fact, most branches (for UI updating) in this function are skipped. The only line that is effective before the logic processes to next step is

1
2
3
4
5
6
7
...
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
...

_renderSubtreeIntoContainer@renderers/dom/client/ReactMount.js

Now that it should be easy to see how the target object of this step is constructed with React.createElement, the function we just examined in the last section.

instantiateReactComponent() - create a ReactCompositeComponent using ReactElement[2]

The purpose of this is step is to create a primitive ReactCompositeComponent for the top level component:

Figure 3.

The call stack of this step is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer()
|-ReactMount._renderNewRootComponent(
nextWrappedElement, // scr:------------------> ReactElement[2]
container, // scr:------------------> document.getElementById('root')
shouldReuseMarkup, // scr: null from ReactDom.render()
nextContext, // scr: emptyObject from ReactDom.render()
)
|-instantiateReactComponent(
node, // scr:------------------> ReactElement[2]
shouldHaveDebugID /* false */
)
|-ReactCompositeComponentWrapper(
element // scr:------------------> ReactElement[2]
);
|=ReactCompositeComponent.construct(element)

instantiateReactComponent is the only long function that is worth discussing here. In our context, it checks the ReactElement[2].type (i.e., TopLevelWrapper) and creates a ReactCompositeComponent accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;

...
} else if (typeof node === 'object') {
var element = node;
var type = element.type;
...

// Special case string values
if (typeof element.type === 'string') {
...
} else if (isInternalComponentType(element.type)) {
...
} else {
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
...
} else {
...
}

...
return instance;
}

instantiateReactComponent@renderers/shared/stack/reconciler/instantiateReactComponent.js

It is worth noting that new ReactCompositeComponentWrapper() is a direct call of ReactCompositeComponent constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
// To avoid a cyclic dependency, we create the final class in this module
var ReactCompositeComponentWrapper = function(element) {
this.construct(element);
};
...

...
Object.assign(
ReactCompositeComponentWrapper.prototype,
ReactCompositeComponent,
{
_instantiateReactComponent: instantiateReactComponent,
},
);
...

ReactCompositeComponentWrapper@renderers/shared/stack/reconciler/instantiateReactComponent.js

Then the real constructor get called:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  construct: function(element) {
this._currentElement = element; // scr:------------> ReactElement[2]
this._rootNodeID = 0;
this._compositeType = null;
this._instance = null;
this._hostParent = null;
this._hostContainerInfo = null;

// See ReactUpdateQueue
this._updateBatchNumber = null;
this._pendingElement = null;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;

this._renderedNodeType = null;
this._renderedComponent = null;
this._context = null;
this._mountOrder = 0;
this._topLevelWrapper = null;

// See ReactUpdates and ReactUpdateQueue.
this._pendingCallbacks = null;

// ComponentWillUnmount shall only be called once
this._calledComponentWillUnmount = false;
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

We name the object created in this step ReactCompositeComponent[T] (T for top).

After the ReactCompositeComponent object is constructed, the next step is to call batchedMountComponentIntoNode, initialize ReactCompositeComponent[T] and mount it, which will be discussed in detail in the next section.

Files used in this section:

A complete static call hierarchy:

1
2
3
4
5
6
7
8
9
10
11
12
13
|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer() |
|-ReactMount._renderNewRootComponent() |
|-instantiateReactComponent() |
|~batchedMountComponentIntoNode() upper half
|~mountComponentIntoNode() (platform agnostic)
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent.mountComponent() |
|-ReactCompositeComponent.performInitialMount() |
|-instantiateReactComponent() _|_
|-ReactDOMComponent.mountComponent() lower half
|-_mountImageIntoNode() (HTML DOM specific)
_|_

batchedMountComponentIntoNode() does not do much. It simply invokes another function call to mountComponentIntoNode().

For now let’s omit the delicacy of those indirect function calls and just see them as direct ones. I will cover transaction and batched updates in later sections.

The cross point of the two halves

mountComponentIntoNode() is the cross point of logic that consists of platform agnostic code (in this article referred as “upper half”) and logic that is HTML specific (lower half). All major tasks of this walk-through are also effectively completed within this function (and its sub-calls), from where 1) a ReactDOMComponent is derived from the top level ReactCompositeComponent[T]; 2) the ReactDOMComponent is rendered to a real DOM element, and 3) the element is inserted into the document object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function mountComponentIntoNode(
wrapperInstance, // scr: -----> ReactCompositeComponent[T]
container, // scr: -----> document.getElementById("root")
transaction, // scr: -----> not of interest
shouldReuseMarkup, // scr: -----> null
context, // scr: -----> emptyObject
) {
...
var markup = ReactReconciler.mountComponent( // scr: -----> 1),2)
wrapperInstance,
transaction,
null,
ReactDOMContainerInfo(wrapperInstance, container),
context,
0 /* parentDebugID */,
);
...
ReactMount._mountImageIntoNode( // scr: -----> 3)
markup,
container,
wrapperInstance,
shouldReuseMarkup,
transaction,
);

ReactMount@renderers/dom/client/ReactMount.js

In which, 1) is still conducted as a high level operation in upper half, while 2), 3) are concrete DOM operations. After 2) completes, we will be able to see the element <h1 style={"color":"blue"}>hello world</h1> rendered on the screen.

For 1), ReactReconciler.mountComponent() is yet another simple function that calls the corresponding mountComponent() of internalInstance passed to it, in this case, ReactCompositeComponent[T].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mountComponent: function(
internalInstance,
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID, // 0 in production and for roots
) {
var markup = internalInstance.mountComponent(
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID,
);
... // scr: transaction related code
return markup;
},

ReactReconciler@renderers/shared/stack/reconciler/ReactReconciler.js

One special parameter here is ReactDOMContainerInfo, it is constructed at the same time when it is passed down to ReactReconciler.mountComponent():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ReactDOMContainerInfo(topLevelWrapper, node) {
var info = {
_topLevelWrapper: topLevelWrapper, // scr: -------------------> ReactCompositeComponent[T]
_idCounter: 1,
_ownerDocument: node ? node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument : null, // scr: -----> node.nowerDocument
_node: node, // src: -----> document.getElementById("root")
_tag: node ? node.nodeName.toLowerCase() : null, // scr: -----> 'div'
_namespaceURI: node ? node.namespaceURI : null // scr: ----->
element.namespaceURI
};
... // scr: DEV code
return info;
}

ReactDOMContainerInfo@renderers/dom/client/ReactMount.js

The result object ReactDOMContainerInfo[ins] will be used by 3).

Here is the end of the corridor that consists of transient functions. Now we move on to the next important stop.

Initialize ReactCompositeComponent[T]

This is where the magic, I mean, the reaction happens

In the previous step only _currentElement of ReactCompositeComponent[T] is populated with a reference to ReactElement[2], which makes the object a little bit dull. But not anymore. This property will in turn be used to trigger a “reaction” within ReactCompositeComponent[T] and transforms (initialize) the object to something more meaningful.

The designated data structure of this step is:

Figure 4.

The call stack in action is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer()
|-ReactMount._renderNewRootComponent()
|-instantiateReactComponent()
|~batchedMountComponentIntoNode(
componentInstance, // scr: -----> ReactCompositeComponent[T]
container, // scr: -> document.getElementById("root")
shouldReuseMarkup, // scr: -----> null
context, // scr: -----> emptyObject
)
|~mountComponentIntoNode(
wrapperInstance, // scr: -----> ReactCompositeComponent[T]
container, // scr: -----> same
transaction, // scr: -----> not of interest
shouldReuseMarkup, // scr: ---> same
context, // scr: -----> not of interest
)
|-ReactReconciler.mountComponent(
internalInstance, // scr: --> ReactCompositeComponent[T]
transaction, // scr: --> not of interest
hostParent, // scr: --> null
hostContainerInfo,// scr: --> ReactDOMContainerInfo[ins]
context, // scr: --> not of interest
parentDebugID, // scr: --> 0
)
/* we are here */
|-ReactCompositeComponent[T].mountComponent(same)

Next let’s look at the ReactCompositeComponent.mountComponent() implementation.

I will not list the implementation for small functions (when they are get called) but give return value directly for the sake of concision.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
// scr: this ------> ReactCompositeComponent[T]
) {
// scr: --------------------------------------------------------> 1)
this._context = context; // scr: -----> emptyObject
this._mountOrder = nextMountID++; // scr: ----------------------> global veriable, accumulative
this._hostParent = hostParent; // scr: -----> null
this._hostContainerInfo = hostContainerInfo; // scr: -----------> ReactDOMContainerInfo[ins]
var publicProps = this._currentElement.props; // scr: ----------> { child: ReactElement[1] }
var publicContext = this._processContext(context); // scr: -----> meaning less, emptyObject
// scr: --------------------------------------------------------> 2)
var Component = this._currentElement.type; // scr: -------------> TopLevelWrapper
var updateQueue = transaction.getUpdateQueue(); // scr: --------> not of interest
// Initialize the public class
var doConstruct = shouldConstruct(Component); // scr: ----------> true, for TopLevelWrapper.prototype.isReactComponent = {};
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
); // scr: ----------> call TopLevelWrapper’s constructor
var renderedElement;
// Support functional components
if (!doConstruct && (inst == null || inst.render == null)) {
...
} else {
if (isPureComponent(Component)) { // scr: --------------------> TopLevelWrapper.prototype.isPureReactComponent is not defined
...
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
// scr: --------------------------------------------------------> 3)
// These should be set up in the constructor, but as a convenience
// for simpler class abstractions, we set them up after the fact.
inst.props = publicProps; // scr: ----> { child: ReactElement[1] }
...
// scr: --------------------------------------------------------> 4)
this._instance = inst; // scr: ---------------------------------> link the ReactCompositeComponent[T] to the TopLevelWrapper instance
// Store a reference from the instance back to the internal representation
ReactInstanceMap.set(inst, this); // scr: ----------------------> link the TopLevelWrapper instance back to ReactCompositeComponent[T]
...
var markup;
if (inst.unstable_handleError) { // scr: -----------------------> false, TopLevelWrapper.prototype.unstable_handleError is not defined
...
} else {
// scr: --------------------------------------------------------> 5)
markup = this.performInitialMount( // scr: a initial at the end?
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
);
}
...
return markup;
}

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

Running in the context of ReactCompositeComponent[T] (as a member function), this method

1) assigns the parameters directly to the corresponding properties of ReactCompositeComponent[T] and local variables. The interesting one variable here is publicProps. It will be used very soon;

2) extracts TopLevelWrapper from this._currentElement.type, and calls its constructor to create a TopLevelWrapper instance. In this process:

1
shouldConstruct(Component)

checks if TopLevelWrapper.prototype.isReactComponent is set, and returns true if so; and

1
this._constructComponent()

calls the TopLevelWrapper constructor if true is the result of the previous function. I will name the instance TopLevelWrapper[ins] this time.;

This is the time you may want to check the definition of TopLevelWrapper, search for ***.

3) initializes the TopLevelWrapper[ins].props of the new instance with the information stored previously in the wrapper element ReactElement[2] through the publicProps in 1);

4) creates a doubly link between this (ReactCompositeComponent[T]) and TopLevelWrapper[ins]. One link is created using this._instance, another is made with ReactInstanceMap. this._instance will be used very soon in the next step, and ReactInstanceMap will be used in later sections.

I type 4 stars **** here as you might need to come back to check ReactInstanceMap‘s origin.

5) goes to the next step.

Create a ReactDOMComponent from ReactElement[1]

This step strips the wrapper and creates a ReactDOMComponent instance. Plus, we are going to see ReactHostComponent the tirst time. This component is used to chain the function call from the upper half to lower half.

First thing first, the target data structure:

Figure 5.

The call stack in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer()
|-ReactMount._renderNewRootComponent()
|-instantiateReactComponent()
|~batchedMountComponentIntoNode()
|~mountComponentIntoNode()
|-ReactReconciler.mountComponent()
|-ReactCompositeComponent[T].mountComponent(same)
/* we are here */
|-ReactCompositeComponent[T].performInitialMount(
renderedElement, // scr: -------> undefined
hostParent, // scr: -------> null
hostContainerInfo, // scr: -------> ReactDOMContainerInfo[ins]
transaction, // scr: -------> not of interest
context, // scr: -------> not of interest
)

ReactCompositeComponent.performInitialMount() does three things. It 1) extracts the ReactElement[1] as mentioned before; 2) instantiates a ReactDOMComponent based on the ReactElement[1].type; and 3) calls ReactDOMComponent.mountComponent() to render a DOM element.

why a performInitialMount is invoked at the end of the mountComponent function? Because it is “initial” for the next mountComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
performInitialMount: function(
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
) {
var inst = this._instance;
var debugID = 0;
if (inst.componentWillMount) { // scr: ----------> undefined

}
// scr: ------------------------------------------------------> 1)
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent(); // scr: ---> calls TopLevelWrapper.render() to extract ReactElement[1].
}
// scr: ------------------------------------------------------> 2)
var nodeType = ReactNodeTypes.getType(renderedElement); // scr: -> ReactNodeTypes.HOST
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
this._renderedComponent = child;
// scr: ------------------------------------------------------> 3)
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID,
);
return markup;
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

Now I give a detailed explanation for each step:

1) this._renderValidatedComponent() simply calls TopLevelWrapper.render() so that ReactElement[1] extracted from TopLevelWrapper[T].props.child is assigned to renderedElement.

2) this._instantiateReactComponent() is an alias to _instantiateReactComponent@renderers/shared/stack/reconciler/instantiateReactComponent.js that has been discussed in the last section. However, renderedElement(i.e., ReactElement[1]) is used to create a ReactDOMComponent instance this time…

wait here,

if we look at the implementation of _instantiateReactComponent() again

1
2
3
4
5
6
7
8
...
// Special case string values
if (typeof element.type === ‘string’) { // scr: -------> this time
instance = ReactHostComponent.createInternalComponent(element);
}
...

_instantiateReactComponent@renderers/shared/stack/reconciler/instantiateReactComponent.js

the createInternalComponent() of ReactHostComponent is got called because ReactElement[1].type is “h1”, which instantiate a genericComponentClass:

1
2
3
4
5
6
function createInternalComponent(element) {
...
return new genericComponentClass(element);
}

ReactHostComponent@renderers/shared/stack/reconciler/ReactHostComponent.js

but why it has anything to do with ReactDOMComponent?

In fact, the platform specific component ReactDOMComponent is “injected” to ReactHostComponent as genericComponentClass during compiling time. For now we consider link has already been established and injection mechanism will be discussed later sections.

I type *5 here.

The constructor of ReactDOMComponent is very similar to that of ReactCompositeComponent:

1
2
3
4
5
6
7
8
9
function ReactDOMComponent(element) {
var tag = element.type; // scr: --------> 'h1'
...
this._currentElement = element; // scr: --------> ReactElement[1]
this._tag = tag.toLowerCase(); // scr: --------> 'h1'
... // scr: default values, null, 0, etc.
}

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

We name the returned instance ReactDOMComponent[ins];

3) ReactReconciler.mountComponent() has already been covered, it simply calls the mountComponent() of the first parameter, in this case, ReactDOMComponent[ins].

Now the logic processes to lower half.

Component Mount (Straight, Lower Half)

We completed the platform agnostic logic (a.k.a., upper half) that embeds ReactElement[1] into ReactCompositeComponent[T] and then uses it to derive ReactDOMComponent[ins].

This time I will discuss how a renderable HTML DOM element is created with ReactDOMComponent[ins], and complete the JSX-to-UI process.

Files used in this section:

renderers/dom/shared/ReactDOMComponent.js: creates the renderable h1 DOM element

renderers/dom/client/utils/DOMLazyTree.js: adds the h1 to the DOM tree

renderers/dom/client/ReactMount.js: the cross point revisited by the above two actions

Create the DOM element with document.createElement()

It calls HTML DOM APIs and hits the bottom.

The designated data structure:

Figure 6.

The call stack in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer() |
|-ReactMount._renderNewRootComponent() |
|-instantiateReactComponent() |
|~batchedMountComponentIntoNode() upper half
|~mountComponentIntoNode() (platform agnostic)
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent.mountComponent() |
|-ReactCompositeComponent.performInitialMount() |
|-instantiateReactComponent() _|_
/* we are here*/ |
|-ReactDOMComponent.mountComponent( lower half
transaction, (HTML DOM specific)
hostParent, |
hostContainerInfo, |
context, (same) |
) |

ReactDOMComponent.mountComponent() is a fairly long and complex function. So I dye the required fields for easily back tracing their origins. Next we look at how exactly it is implemented.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
mountComponent: function (
transaction, // scr: -----> not of interest
hostParent, // scr: -----> null
hostContainerInfo, // scr: -----> ReactDOMContainerInfo[ins]
context // scr: -----> not of interest
) {

// scr: --------------------------------------------------------> 1)
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;

var props = this._currentElement.props;

switch (this._tag) { // scr: ---> no condition is met here
...
}

... // scr: -----> sanity check

// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var namespaceURI;
var parentTag;

if (hostParent != null) { // scr: -----> it is null
...
} else if (hostContainerInfo._tag) {
namespaceURI = hostContainerInfo._namespaceURI; // scr: -------> "http://www.w3.org/1999/xhtml"
parentTag = hostContainerInfo._tag; // scr: ------> "div"
}
if (namespaceURI == null ||
namespaceURI === DOMNamespaces.svg &&
parentTag === 'foreignobject'
) { // scr: -----> no
...
}

if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'svg') { // scr: -----> no
...
} else if (this._tag === 'math') { // scr: -----> no
...
}
}

this._namespaceURI = namespaceURI; // scr: ---------------------> "http://www.w3.org/1999/xhtml"

... // scr: ------> DEV code

var mountImage;

if (transaction.useCreateElement) { // scr: ---------------------> transaction related logic, we assume it is true
var ownerDocument = hostContainerInfo._ownerDocument;
var el;

if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'script') { // scr: -----> no
...
} else if (props.is) { // scr: -----> no
...
} else {
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240

// scr: --------------------------------------------------------> 2)
// scr: ---------> HTML DOM API
el = ownerDocument.createElement(this._currentElement.type);
}
} else { // scr: ------> no
...
}

// scr: --------------------------------------------------------> 3)
ReactDOMComponentTree.precacheNode(this, el); // scr: --------> doubly link (._hostNode & .internalInstanceKey)
this._flags |= Flags.hasCachedChildNodes; // scr: ------------>
bit wise its flags

// scr: --------------------------------------------------------> 4)
if (!this._hostParent) { // scr: ------> it is the root element
DOMPropertyOperations.setAttributeForRoot(el); // scr: -----> data-reactroot
}

// scr: --------------------------------------------------------> 5)
this._updateDOMProperties( //*6
null,
props,
transaction
); // scr: --------------------------> style:{ “color”: “blue” }

// scr: --------------------------------------------------------> 6)
var lazyTree = DOMLazyTree(el); // scr: ------> DOMLazyTree[ins]

// scr: --------------------------------------------------------> 7)
this._createInitialChildren( //*7
transaction,
props,
context,
lazyTree
); // scr: --------------------------> textContent:‘hello world’

mountImage = lazyTree;
} else { // if (transaction.useCreateElement)
...
}

switch (this._tag) { // scr: ---> no condition is met here
...
}

return mountImage;
}

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

1) It initializes some of the ReactDOMComponent[ins]’s properties with the parameters and global variables. Then all the if conditions are passed until

2) The HTML DOM API document.createElement() is called to create an h1 DOM element.

3) ReactDOMComponentTree.precacheNode() is used to create a doubly link between ReactDOMComponent[ins] and h1 DOM element, it links ReactDOMComponent[ins]._hostNode to the h1 DOM element and the element’s internalInstanceKey back to ReactDOMComponent[ins].

4) As a null _hostParent indicates an internal root component, DOMPropertyOperations.setAttributeForRoot() set element.data-reactroot to empty string "", to mark a root element externally.

5) _updateDOMProperties is a complex method. For now we only need to know the method extracts { “color”: “blue” } from ReactDOMComponent[ins]._currentElement.props and attaches it to the DOM’s style attribute. We will come back to this method in more detail when discussing “setState() triggered UI update”. Search for

*6

in this text when you need check when this function is used first time.

6) Instantiates a DOMLazyTree[ins].

7) _createInitialChildren is another complex method. For now we only need to know the method extracts 'hello world' from ReactDOMComponent[ins]._currentElement.children and attaches it to the DOM’s textContent. We will come back to this method in more detail when discussing “composite component rendering”. You can search for

*7

then.

Then DOMLazyTree[ins] is returned all the way back to the cross point discussed in the last section — mountComponentIntoNode().

Mount the DOM into the container node

First lemme copy back the stack frame from the last section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer() |
|-ReactMount._renderNewRootComponent() |
|-instantiateReactComponent() |
|~batchedMountComponentIntoNode() upper half
|~mountComponentIntoNode( (platform agnostic)
wrapperInstance, // scr: -> not of interest now |
container, // scr: --> document.getElementById(‘root’)
transaction, // scr: --> not of interest |
shouldReuseMarkup, // scr: -------> null |
context, // scr: -------> not of interest
) |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent.mountComponent() |
|-ReactCompositeComponent.performInitialMount() |
|-instantiateReactComponent() _|_
|-ReactDOMComponent.mountComponent() |
/* we are here */ lower half
|-_mountImageIntoNode() (HTML DOM specific)
markup, // scr: --> DOMLazyTree[ins] |
container, // scr: --> document.getElementById(‘root’)
wrapperInstance, // scr:----> same |
shouldReuseMarkup, // scr:--> same |
transaction, // scr: -------> same |
) _|_

Now the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
_mountImageIntoNode: function (
markup,
container,
instance,
shouldReuseMarkup,
transaction)
{
if (shouldReuseMarkup) { // scr: -------> no

}

if (transaction.useCreateElement) {//scr:>again, assume it is true
while (container.lastChild) { // scr: -------> null

}

// scr: -------------------------> the only effective line this time
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {

}

// scr: DEV code
}

ReactMount@renderers/dom/client/ReactMount.js

Despite the seemly complexity, there is only one line that is effective, that calls insertTreeBefore.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function (
parentNode, // scr: -----> document.getElementById(‘root’)
tree, // scr: -----> DOMLazyTree[ins]
referenceNode // scr: -----> null
) {
if (tree.node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE ||
tree.node.nodeType === ELEMENT_NODE_TYPE &&
tree.node.nodeName.toLowerCase() === 'object' &&
(tree.node.namespaceURI == null ||
tree.node.namespaceURI === DOMNamespaces.html)) { // scr:->no
...
} else {
parentNode.insertBefore(tree.node, referenceNode);
insertTreeChildren(tree); // scr: -> returned directly in Chrome
}
});

DOMLazyTree@renderers/dom/client/utils/DOMLazyTree.js

Inside this function, the only one line that is effective is:

1
parentNode.insertBefore(tree.node, referenceNode);

which is another HTML DOM API that inserts DOMLazyTree[ins].node (i.e., the h1 DOM element) into the #root element, as instructed with the JSX statement in the very beginning :

1
2
3
4
5
6

ReactDOM.render(
<h1 style={{“color”:”blue”}}>hello world</h1>,
document.getElementById(‘root’)
);

Here is the big pircure we have so far

Figure 7. `React.createElement()` — create a `ReactElement`

Figure 8. `_renderSubtreeIntoContainer()` — attach `TopLevelWrapper` to the `ReactElement[1]`

Figure 9. `instantiateReactComponent()` — create a `ReactCompositeComponent` using `ReactElement[2]`

Figure 10. `ReactCompositeComponent.mountComponent()` — initialize `ReactCompositeComponent[T]`

Figure 11. `ReactCompositeComponent.performInitialMount()` — create a `ReactDOMComponent` from `ReactElement[1]`

Figure 12. `ReactDOMComponent.mountComponent()` — create the DOM element with document.createElement()

Figure 13. `mountImageIntoNode()` — mount the DOM into the container node

In this series I focus on a very primitive operation — h1 component mount, which nonetheless walk through the complete critical-path of the rendering process. I think this quick end-to-end tour can help establishing an initial knowledge base and confidence for further exploring the uncharted areas. I assume it’s gonna be more intricate and intriguing.

Though good effort is made to simulate a real experience that includes a clear goal and a concrete outcome, it is highly recommended to try the full version of the source code by debugging it in Chrome directly.

Component Mount (Class, Upper Half)

Files used in this section: the same as section I

I use {} to reference the previous sections that is relevant to the methods (or logic process) being discussed.

The component named App is similar to what I gave in the beginning of section I, in which we considered it too complex for beginners. But since we have leveled-up a bit, it does not look that daunting anymore.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { Component } from ‘react’;
import logo from ‘./logo.svg’;
import ‘./App.css’;

class App extends Component {
constructor(props) {
super(props);
this.state = {
desc: 'start',
};
}

render() {
return (
<div className="App">
<div className="App-header">
<img src="main.jpg" className="App-logo" alt="logo" />
<h1> "Welcom to React" </h1>
</div>
<p className="App-intro">
{ this.state.desc }
</p>
</div>
);
}
}

export default App;

App@App.js

As mentioned, the component above is rendered using:

1
2
3
4
ReactDOM.render(
<App />,
document.getElementById(‘root’)
);

Now the babeled code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
constructor(props) {
super(props);
this.state = {
desc: 'start',
};
}

render() {
return React.createElement(
'div',
{ className: 'App' },
React.createElement(
'div',
{ className: 'App-header' },
React.createElement(
'img',
{ src: "main.jpg", className: 'App-logo', alt: 'logo' }
),
React.createElement(
'h1',
null,
' "Welcom to React" '
)
),
React.createElement(
'p',
{ className: 'App-intro' },
this.state.desc
)
);
}
}

export default App;

...

ReactDOM.render(React.createElement(App, null), document.getElementById('root'));

Here we consider Component a common base class, as other methods will not be used in this section.

This time we can fast forward the logic that is shared with simple component.

Create ReactCompositeComponent[T]

top level wrapper

The designated data structure:

Figure 14.

This step is almost the same as that in simple component rendering, so I will give a brief description only, it

1) creates ReactElement[1] using ReactElement.createElement(type, config, children) (This time App is passed to type, and config, children are null);

  1. creates ReactElement[2] in _renderSubtreeIntoContainer();

  2. creates the designated wrapper with instantiateReactComponent().

1
2
3
4
ReactElement.createElement(type,    // scr: -------------> App
config, // scr: -------------> null
children // scr: -------------> null
) // scr: ------------------------------------------------------> 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer(
parentComponent, // scr: ----> null
nextElement, // scr: ----> ReactElement[1]
container, // scr: ----> document.getElementById(‘root’)
callback’ // scr: ----> undefined
) // scr: ------------------------------------------------------> 2)
|-instantiateReactComponent( // scr: -------------------------> 3)
node, // scr: ------> ReactElement[2]
shouldHaveDebugID /* false */
)
|-ReactCompositeComponentWrapper(
element // scr: ------> ReactElement[2]
);
|=ReactCompositeComponent.construct(element /* same */)

This is what we covered in {section I}.

Create ReactCompositeComponent[T]

The designated data structure:

Figure 15.

The step is the same as well:

1) ReactDOMContainerInfo[ins] represents the container DOM element, document.getElementById(‘root’);

2) TopLevelWrapper is instantiated (TopLevelWrapper[ins]) and is set to ReactCompositeComponent[T]._instance alongside the initialization of other properties;

3) Again, mountComponentIntoNode is the cross point of upper and lower half, within which ReactCompositeComponent[T].mountComponent returns a complete DOMLazyTree that can be used by ReactMount._mountImageIntoNode, a method from lower half.

1
2
3
4
5
6
7
8
9
10
11
12
ReactDOM.render                                           ___
|=ReactMount.render(nextElement, container, callback) |
|=ReactMount._renderSubtreeIntoContainer() |
|-ReactMount._renderNewRootComponent() |
|-instantiateReactComponent() |
|~batchedMountComponentIntoNode() upper half
|~mountComponentIntoNode() (platform agnostic)
|-ReactReconciler.mountComponent() // scr-----> 1) |
|-ReactCompositeComponent[T].mountComponent() scr:> 2)3)
... _|_
... lower half
|-_mountImageIntoNode() (HTML DOM specific)

This is what we covered in the first part of {section I}.

Except for some small differences in regard to argument values, the the top level wrapper related operations are exactly the same as what we discussed in previous sections. After those operations complete, the logic processes to the first ramification that is specific to class component.

Create a ReactCompositeComponent from ReactElement[1]

This step strips the wrapper and creates another ReactCompositeComponent instance to reflect App component.

The designated data structure:

Figure 16.

The call stack in action:

1
2
3
4
5
6
7
8
9
10
11
12
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
/* we are here */ |
|-ReactCompositeComponent[T].performInitialMount( |
renderedElement, // scr: -------> undefined |
hostParent, // scr: -------> null upper half
hostContainerInfo, // scr: -------> | ReactDOMContainerInfo[ins] |
transaction, // scr: -------> not of interest |
context, // scr: -------> not of interest |
) |

The process is very similar to the performInitialMount() in {section I}. The only difference here is that based on ReactElement[1].type, _instantiateReactComponent creates a ReactCompositeComponent for the class component (App) instead of a ReactDOMComponent. To put it briefly:

1) it calls _renderValidatedComponent() which in turn calls TopLevelWrapper.render() to extract ReactElement[1]; 2) it instantiates a ReactCompositeComponent with _instantiateReactComponent (we name the object ReactCompositeComponent[ins]); and 3) it calls ReactCompositeComponent[ins].mountComponent (recursively) through ReactReconciler, and moves on to the next step.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
performInitialMount: function (
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context)
{
var inst = this._instance;

...

if (inst.componentWillMount) {
... // scr: we did not define componentWillMount() in App
}

// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent(); // scr: > 1)
}

var nodeType = ReactNodeTypes.getType(renderedElement); // scr: -> the type is ReactNodeTypes.Composite this time

this._renderedNodeType = nodeType;

var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
); // scr: ----------------------------------------------> 2)

this._renderedComponent = child;

var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID); // scr: ----------------------------------------------> 3)

...// scr: DEV code

return markup;
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

Create ReactCompositeComponent[ins]

The designated data structure:

Figure 17.

The call stack in action:

1
2
3
4
5
6
7
8
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
/* we are here */ |
|-ReactCompositeComponent[ins].mountComponent(same) |

Same as in ReactCompositeComponent[T].mountComponent() {section I}, the most important task of this step is to instantiate App with ReactCompositeComponent[ins]._currentElement (ReactElement[1]).

The line in the method that does the job is:

1
2
3
4
5
6
7
8
9
10
...
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
);
...

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

in which the constructor of App gets called.

1
2
3
4
5
6
7
8
9
10
...
constructor(props) {
super(props);
this.state = {
desc: 'start',
};
}
...

// copied from the beginning of this text

Then (we name it) App[ins] is set to ReactCompositeComponent[ins]._instance and a back-link is also created through ReactInstanceMap.

This is the custom component App instance which responds to your this.setState(…).

Other operations includes: 1) App[ins].props reference ReactElement[1].props; and 2) ReactCompositeComponent[ins]._mountOrder is set to 2 due to the ++ operating on the global variable nextMountID.

It is important to note that App[ins].render() is another App method we define in the beginning. Unlike TopLevelWrapper[ins].render() that returns a concrete ReactElement instance, App[ins].render() relies on React.createElement() at the time when it is invoked. We are going to visit this method soon.

Since this step is very similar to that initializes the ReactCompositeComponent[T] {section I}, we do not further examine the workhorse method (i.e., mountComponent()).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
// scr: this ------> ReactCompositeComponent[T]
) {
...
this._mountOrder = nextMountID++; // scr: --------------------> 2)
...
var publicProps = this._currentElement.props; // scr: -----------> { child: ReactElement[1] }
...
// scr: -------------------------------------------------> critical)
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
); // scr: ----------> call TopLevelWrapper’s constructor
var renderedElement;
...
// These should be set up in the constructor, but as a convenience
// for simpler class abstractions, we set them up after the fact.
// scr: --------------------------------------------------------> 1)
inst.props = publicProps; // scr: ----> { child: ReactElement[1] }

// scr: -----------------------------------------------> critical)
this._instance = inst; // scr: ---------------------------------> link the ReactCompositeComponent[T] to the TopLevelWrapper instance
// Store a reference from the instance back to the internal representation
ReactInstanceMap.set(inst, this); // scr: ----------------------> link the TopLevelWrapper instance back to ReactCompositeComponent[T]

var markup;
if (inst.unstable_handleError) { // scr: -----------------------> false, TopLevelWrapper.prototype.unstable_handleError is not defined

} else {
// scr: -------------------------------------------------> next)
markup = this.performInitialMount( // scr: a initial at the end?
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
);
}


return markup;
}

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

Create a ReactDOMComponent

1
2
3
4
5
6
7
8
9
10
11
12
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
/* we are here */ |
|-ReactCompositeComponent[ins].mountComponent() |
|-this.performInitialMount() |
|-this._renderValidatedComponent() |
|-instantiateReactComponent() _|_
|-ReactDOMComponent[6].mountComponent() lower half

We are here again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
performInitialMount: function (
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context)
{
var inst = this._instance;
...
if (inst.componentWillMount) {
... // scr: we did not define componentWillMount() in App
}
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent(); // scr: > 1)
}
var nodeType = ReactNodeTypes.getType(renderedElement); // scr: -> the type is ReactNodeTypes.Host this time
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
); // scr: ----------------------------------------------> 2)
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID); // scr: ----------------------------------------------> 3)
...// scr: DEV code
return markup;
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

Before the a ReactDOMComponent (we know that this is the class that handle DOM operations) can be created, the ReactElements defined within App[ins] needs to be extracted. To do so, App[ins].render() is called by the following line (in _renderValidatedComponent()) {section I}

1
2
3
...
renderedElement = this._renderValidatedComponent();
...

Then App[ins].render() triggers

The cascading calls of React.createElement()

To understand how the ReactElement tree is established, let’s first revisit the App.render() implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
render() {
return React.createElement( // scr: -----------> 5)
'div',
{ className: 'App' },
React.createElement( // scr: -----------> 3)
'div',
{ className: 'App-header' },
React.createElement( // scr: -----------> 1)
'img',
{ src: "main.jpg", className: 'App-logo', alt: 'logo' }
),
React.createElement( // scr: -----------> 2)
'h1',
null,
' "Welcom to React" '
)
),
React.createElement( // scr: -----------> 4)
'p',
{ className: 'App-intro' },
this.state.desc
)
);
}

// copied from the beginning of this text

In this code snippet I also give the call order of createElement()s which follows a very simple principle: arguments should be resolved (with createElement()) from left to right before a function (of createElement()) gets called.

Then we can examine the creation of each ReactElement {section I}.

1
2
3
4
React.createElement( // scr: --------------------------------> 1)
‘img’,
{ src: "main.jpg", className: ‘App-logo’, alt: ‘logo’ }
),

creates ReactElement[2]:

Figure 18.

; and

1
2
3
4
5
React.createElement( // scr: --------------------------------> 2)
‘h1’,
null,
‘Welcome to React’
)

creates ReactElement[3]:

Figure 19.

(Now the two arguments for 3) are resolved.)

; and

1
2
3
4
5
React.createElement(                // scr: -----------> 3)
'div',
ReactElement[2],
ReactElement[3]
),

creates ReactElement[4]:

Figure 20.

; and

1
2
3
4
5
React.createElement(                // scr: -----------> 4)
'p',
{ className: 'App-intro' },
this.state.desc
)

creates ReactElement[5]:

Figure 21.

(Now the arguments for 5) are resolved.)

; and

1
2
3
4
5
6
return React.createElement(           // scr: -----------> 5)
'div',
{ className: 'App' },
ReactElement[4],
ReactElement[5]
)

creates ReactElement[6]:

Figure 22.

Combined together we got the element tree referenced by renderedElement:

Figure 23.

ReactCompositeComponent[ins]._instantiateReactComponent() — Create ReactDOMComponent[6]

The designated data structure:

Figure 24.

The element tree created in the last step is used to create the ReactDOMComponent[6] by the following line (within _instantiateReactComponent()) {section I}

1
2
3
4
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);

Now ReactReconciler.mountComponent() calls the mountComponent() of the ReactDOMComponent[6] and the logic processes to the lower half.

Component Mount (Class, Lower Half)

Last time we completed the upper half of the class component rendering logic which is different from simple component rendering in the following respects: 1) it instantiates one additional ReactCompositeComponent to represent the class component (App); and 2) it calls App.render() that triggers cascading React.createElement()s to establish a ReactElement tree.

This time we are going to explore more branches in lower half by examining how the ReactElements in the tree is transformed to their respective ReactDOMComponents, and eventually, to real DOM objects.

Files used in this section:

renderers/dom/shared/ReactDOMComponent.js: offers the one of the methods that this section focuses on, _createInitialChildren

renderers/dom/client/utils/setTextContent.js: DOM operation, setting text

renderers/dom/client/utils/DOMLazyTree.js: DOM operation, appending child

renderers/shared/stack/reconciler/ReactMultiChild.js: the intermediate method to traverseAllChildren as well as the other method in focus, mountChildren()

shared/utils/traverseAllChildren.js: method that iterates direct sub ReactElements and instantiates their respective ReactDOMComponents

Notations used in the call stack:
↻ loop
? condition

I use {} to reference the previous section that is relevant to the methods (or logic process) being discussed.

The process discussed in this section occurs majorly within ReactDOMComponent[6].mountComponent(). The major task of this method, that derives a DOM object from ReactDOMComponent[6], is covered in {section II}. This task is numbered as 0) for later reference.

In this section, we are going to address one of the methods that we overlooked on purpose last time, _createInitialChildren which is used for handling the newly introduced ReactElement tree as the class component’s children. It was used in a niche case, a text child, in {section II *7} and only a side branch was triggered. This side branch, as well as the entire method will be discussed in detail in this section.

_createInitialChildren is our protagonist today; please search *7 in section II if you want to check its role in the simple component rendering. The other overlooked method _updateDOMProperties in {section II *6} will be discussed in later sections.

To be more specific, this method 1) transforms ReactElements to their corresponding ReactDOMComonents; 2) (recursively) calls ReactDOMComponent[*].mountComponent() to create the DOM objects; and 3) appends them to the root DOM node that is created in 0).

So firstly let’s recap step 0) in the context of class component.

Create the DOM element[6] (prior _createInitialChildren)

time-saving hint: this paragraph is here to keep the section self-contained, the detail of the ReactDOMComponent creation process has been covered in {section II}

Designated data structure:

Figure 25.

Call stack in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[ins].mountComponent() |
|-this.performInitialMount() |
|-this._renderValidatedComponent() |
|-instantiateReactComponent() _|_
(we are here) |
|-ReactDOMComponent[6].mountComponent( |
transaction, // scr: -----> not of interest |
hostParent, // scr: -----> null |
hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins] lower half
context // scr: -----> not of interest |
) |
...

This step creates a DOM object with ReactDOMComponent[6], and set up its attributes.

To recap: it 1) initializes properties of ReactDOMComponent[6]; 2) creates a div DOM element using document.createElement(); 3) creates a double link between ReactDOMComponent[6] and the DOM object; 4) & 5) set the properties and attributes of the newly created DOM object; and 6) embeds the DOM object in DOMLazyTree[1].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
mountComponent: function (
transaction,
hostParent,
hostContainerInfo,
context
) {

// scr: --------------------------------------------------------> 1)
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo; // scr: ------------> ReactDOMContainerInfo[ins]

var props = this._currentElement.props;

switch (this._tag) { // scr: ---> no condition is met here
...
}

... // scr: -----> sanity check

// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var namespaceURI;
var parentTag;

if (hostParent != null) { // scr: -----> it is null
...
} else if (hostContainerInfo._tag) {
namespaceURI = hostContainerInfo._namespaceURI; // scr: -------> "http://www.w3.org/1999/xhtml"
parentTag = hostContainerInfo._tag; // scr: ------> "div"
}
if (namespaceURI == null ||
namespaceURI === DOMNamespaces.svg &&
parentTag === 'foreignobject'
) { // scr: -----> no
...
}

if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'svg') { // scr: -----> no
...
} else if (this._tag === 'math') { // scr: -----> no
...
}
}

this._namespaceURI = namespaceURI; // scr: ---------------------> "http://www.w3.org/1999/xhtml"

... // scr: ------> DEV code

var mountImage;

if (transaction.useCreateElement) { // scr: ---------------------> transaction related logic, we assume it is true
var ownerDocument = hostContainerInfo._ownerDocument;
var el;

if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'script') { // scr: -----> no
...
} else if (props.is) { // scr: -----> no
...
} else {
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240

// scr: --------------------------------------------------------> 2)
// scr: ---------> HTML DOM API
el = ownerDocument.createElement(this._currentElement.type);
}
} else { // scr: ------> no
...
}

// scr: --------------------------------------------------------> 3)
ReactDOMComponentTree.precacheNode(this, el); // scr: --------> doubly link (._hostNode & .internalInstanceKey)
this._flags |= Flags.hasCachedChildNodes; // scr: ------------>
bit wise its flags

// scr: --------------------------------------------------------> 4)
if (!this._hostParent) { // scr: ------> it is the root element
DOMPropertyOperations.setAttributeForRoot(el); // scr: -----> data-reactroot
}

// scr: --------------------------------------------------------> 5)
this._updateDOMProperties( //*6
null,
props,
transaction
); // scr: --------------------------> style:{ “color”: “blue” }

// scr: --------------------------------------------------------> 6)
var lazyTree = DOMLazyTree(el); // scr: ------> DOMLazyTree[ins]
this._createInitialChildren(transaction, props, context, lazyTree);
...
} // if (transaction.useCreateElement)

return mountImage;
}

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

ReactDOMComponent[6] gets its DOM node, its children are next in the line

Create DOM elements[2-5]

Designated data structure:

Figure 26.

As mentioned before, this method was used to create the string child node (‘hello world’) in {section II}. We will reuse this side branch in this section when rendering similar nodes (i.e., [3] and [5]), and we name the branch {1}.

In the case of class component, route {2} is hit when this method is accessed the first time. To be specific, this branch handles the ReactElement tree. As mentioned, it 1) transforms ReactElements to ReactDOMComponents (a), generates DOM nodes with ReactDOMComponents (b), and 2) insert DOM nodes to the root node generated with ReactDOMComponent[6] in last step.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
_createInitialChildren: function (
transaction, // scr: not of interest
props, // scr: -------------------> ReactElement[6].props
context, // scr: not of interest
lazyTree // scr: -------------------> DOMLazyTree[ins]
) {
// Intentional use of != to avoid catching zero/false.
// scr: it is named as 'dangerous', let's avoid touching it
var innerHTML = props.dangerouslySetInnerHTML;
if (innerHTML != null) { // scr: so no innerHTML
...
} else {
var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null;

var childrenToUse = contentToUse != null ? null : props.children;
// scr: some comments
if (contentToUse != null) {
// scr: some comments
if (contentToUse !== '') { // scr: ----------------> route {1}
...// scr: DEV code
DOMLazyTree.queueText(lazyTree, contentToUse);
}
} else if (childrenToUse != null) { // scr: ---------> route {2}
var mountImages = this.mountChildren(childrenToUse, transaction, context); // scr: --------------------------------> 1)
for (var i = 0; i < mountImages.length; i++) { scr: ------> 2)
DOMLazyTree.queueChild(lazyTree, mountImages[i]);
}
}
}
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

The call hierarchy and iteration is a bit complex from now on, so this time I’ll first establish an overview of the big picture before diving into any detail.

The static call stack:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...                                            (outer recursion)
ReactDOMComponent[6].mountComponent() <-------------------------|
(we are here) |
|-this._createInitialChildren() |
?{1} |
|-DOMLazyTree.queueText() |
?{2} |
|-this.mountChildren() // scr: ---------------> 1)(a) |
|-this._reconcilerInstantiateChildren() |
|-ReactChildReconciler.instantiateChildren() |
|-traverseAllChildren() |
|-traverseAllChildrenImpl() <------|inner |
|↻traverseAllChildrenImpl() ------|recursion |
|-instantiateChild() |
|-instantiateReactComponent() |
|↻ReactDOMComponent.mountComponent() // scr: -> 1)(b)---|
|↻DOMLazyTree.queueChild() // scr: ---------------> 2)
...

First we examine the bottom of the (complex) stack that operates on the DOM level (by understanding the end purpose, we can be a bit relieved in facing the complex call graph).

DOMLazyTree.queueText() and DOMLazyTree.queueChild()

DOMLazyTree.queueText() has only one effective line in this walk-through:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function queueText(tree, text) {
if (enableLazy) { // scr: NO, I mean, false
...
} else {
setTextContent(tree.node, text);
}
}

queueText@renderers/dom/client/utils/DOMLazyTree.js

var setTextContent = function (node, text) {
if (text) {
var firstChild = node.firstChild;

if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) { // scr: false
...
}
}
node.textContent = text; // scr: the only effective line
};

setTextContent@renderers/dom/client/utils/setTextContent.js

Node.textContent is a DOM standard property that represents, well, text content of a node. Apparently, it is the end purpose of route {1}.

DOMLazyTree.queueChild() has one line as well:

1
2
3
4
5
6
7
8
9
function queueChild(parentTree, childTree) {
if (enableLazy) { // scr: again, false
...
} else {
parentTree.node.appendChild(childTree.node);
}
}

queueChild@renderers/dom/client/utils/DOMLazyTree.js

Here Node.appendChild() is yet another DOM standard API that inserts a node to another as child. Apparently, it is the last stop of route {2}.

Now we can replace these two methods with their respective essence line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...                                          (outer recursion)
ReactDOMComponent[6].mountComponent() <-------------------------|
|-this._createInitialChildren() |
?{1} |
|-node.textContent = text; |
?{2} |
|-this.mountChildren() // scr: ---------------> 1)(a) |
|-this._reconcilerInstantiateChildren() |
|-ReactChildReconciler.instantiateChildren() |
|-traverseAllChildren() |
|-traverseAllChildrenImpl() <------|inner |
|↻traverseAllChildrenImpl() ------|recursion |
|-instantiateChild() |
|-instantiateReactComponent() |
|↻ReactDOMComponent.mountComponent() // scr: ------> 1)(b)---|
|↻node.appendChild() // scr: ------> 2)
...

Big picture

First, instantiateReactComponent() which instantiates a ReactDOMComponent from ReactElement (we do not have any “composite” ReactElement in the tree now so all the components being created are ReactDOMComponents), which is also the end of the deeply nested call hierarchy {section I}

Second, ReactDOMComponent.mountComponent() which initializes the ReactDOMComponents created in the previous step and create the corresponding DOM nodes based on them. {section I} & {beginning}

Considering the above two operations an {OG} (operation group), it is now easier to explain how the rest of the ReactElement tree is processed.

Here is a high-level explanation:

  1. when outer recursion of ReactDOMComponent.mountComponent() is called for non-leaf node, branch {2} will be taken to trigger {OG} for each of the component’s children;

  2. when outer recursion of ReactDOMComponent.mountComponent() is called for leaf node that contains text, branch {1} will be in action, which set node.textContent;

  3. when outer recursion of ReactDOMComponent.mountComponent() is called for leaf node that does not contain text, _createInitialChildren() will not be called at all.

Please note that in this process ReactDOMComponent.mountComponent() is used repeatedly to create DOM node for the respective ReactDOMComponent instance, so you might need to check its implementation in the beginning of this text if it is not in your (brain) cache.

It’s time to draw the call stack in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
ReactDOMComponent[6].mountComponent()
|-this._createInitialChildren()
|-this.mountChildren()
... |↻instantiateReactComponent()[4,5]
|-ReactDOMComponent[5].mountComponent()
|-this._createInitialChildren()
|-node.textContent = text; // scr: [5] done
|-ReactDOMComponent[4].mountComponent()
|-this._createInitialChildren()
|-this.mountChildren()
... |↻instantiateReactComponent()[2,3]
|-ReactDOMComponent[2].mountComponent() // scr: [2] done
|-ReactDOMComponent[3].mountComponent()
|-this._createInitialChildren()
|-node.textContent = text; // scr: [3] done
|↻node[4].appendChild()[2,3] // scr: [4] done

|↻node[6].appendChild()[4,5] // scr: [6] done
...

In this call stack I omit the deep nested call hierarchy that is used to instantiate the ReactDOMComponent from ReactElement as we are going to examine it next.

The deep nested loop of instantiateReactComponent()

In particular, we need to pay attention to the arguments to keep track the input & output across the fairly deep and complex chain that enlists recursion and callback.

Starting from inside ReactDOMComponent._createInitialChildren():

1
2
3
4
5
6
7
...
var mountImages = this.mountChildren(
childrenToUse, // scr:----------> ReactElement[6].props.children
transaction, // scr: not of interest
context // scr: not of interest
);
...

Next we look at the implementation of ReactDOMComponent.mountChildren(). As mentioned before, it 1) instantiates all the children of ReactDOMComponents; and 2) initializes those ReactDOMComponent by calling ReactDOMComponent.mountComponent().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
mountChildren: function (
nestedChildren, // scr:----------> ReactElement[6].props.children
transaction, // scr: not of interest
context // scr: not of interest
) {

// scr: ------------------------------------------------------> 1)
var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
this._renderedChildren = children;
var mountImages = [];
var index = 0;
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
var selfDebugID = 0;
...// scr: DEV code
(outer recursion)
// scr: --------------------------------------------------> 2)
var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID);

child._mountIndex = index++;
mountImages.push(mountImage);
}
}
...// scr: DEV code
return mountImages;
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

2) was referred to as “outer recursion” before and is yet another ReactReconciler.mountComponent() {section I}, so we focus on 1)

1
2
3
4
5
6
7
8
9
10
_reconcilerInstantiateChildren: function (
nestedChildren, // scr:----------> ReactElement[6].props.children
transaction, // scr: not of interest
context // scr: not of interest
) {
...// scr: DEV code
return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context);
},

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

which is a direct call of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
instantiateChildren: function (
nestedChildNodes, // scr: --------> ReactElement[6].props.children
transaction, // scr: not of interest
context, // scr: not of interest
selfDebugID
) // 0 in production and for roots {
if (nestedChildNodes == null) {
return null;
}
var childInstances = {};
if (process.env.NODE_ENV !== 'production') {
...// scr: DEV code
} else {
traverseAllChildren(nestedChildNodes, instantiateChild, childInstances);
}
return childInstances;
},

instantiateChildren@renderers/shared/stack/reconciler/ReactChildReconciler.js

which is, again, a direct call to traverseAllChildren(). Note that instantiateChild is the callback method which is invoked for each child.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function instantiateChild(
childInstances, // scr: ---> the output parameter childInstances is passed all the way down here
child, // scr: --> a ReactElement
name, // scr: --> unique name for indexing in childInstances
selfDebugID // scr: --> undefined
) {
... // scr: DEV code
}
if (child != null && keyUnique) {
childInstances[name] = instantiateReactComponent(child, true);
}
}

instantiateChild@renderers/shared/stack/reconciler/ReactChildReconciler.js

It in turn calls instantiateReactComponent() {section I} directly.

We move on to traverseAllChildren()

1
2
3
4
5
6
7
8
9
10
11
12
function traverseAllChildren(
children, // scr: ---------> ReactElement[6].props.children
callback, // scr: ---------> instantiateChild
traverseContext // scr: ---> output parameter, initialized as {}
) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

traverseAllChildren@shared/utils/traverseAllChildren.js

yet another direct call to traverseAllChildrenImpl()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function traverseAllChildrenImpl(
children, // scr: ---------> ReactElement[6].props.children
nameSoFar, // scr: ---------> ''
callback, // scr: ---------> instantiateChild
traverseContext // scr: ---> output parameter, initialized as {}
) {
var type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
// scr: -------------------------------------------------------> {a}
if (children === null || type === 'string' || type === 'number' || type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) {
callback(traverseContext, children,
// If it's the only child, treat the name as if it was wrapped in an array
// so that it's consistent if the number of children grows.
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
return 1;
}
var child;
var nextName;
var subtreeCount = 0; // Count of children found in the current subtree.
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// scr: -------------------------------------------------------> {b}
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
}
} else {
... // scr: this branch will not be called here
}
return subtreeCount;
}

traverseAllChildrenImpl@shared/utils/traverseAllChildren.js

After those directly one-line calls of another method, traverseAllChildrenImpl() is the true workhouse. this method was also referred to as “inner recursion” soon before.

The logic of traverseAllChildrenImpl() is quite straight forward: when it is called the first time (and the type of children parameter is array), it calls itself for every ReactElement within the array; when it is called successively (children is ReactElement), it invokes the aforementioned callback that internally relies on instantiateReactComponent() {section I} to transform a ReactElement to an empty and uninitialized ReactDOMComonent.

Note that “inner recursion” works on DIRECT children only while the “outer recursion” traverse the ENTIRE ReactElement tree.

After all the ReactElements are transformed to ReactDOMComonents, the output is returned all the way back to ReactDOMComponent.mountChildren() and complete the circle.

To better understand the full circle, you might need to refer to different pieces of the puzzle back and forth, for example, the beginning of this text where ReactDOMComponent.mountComponent() is discussed; the two DOM operations (Node.appendChild, Node.textContent) that define the stack bottom; the discussion of the big picture; as well as this section.

At last, after all the DOM nodes are generated, the logic returns back to ReactReconciler.mountComponent() and the new node tree is inserted to the designated div container. {section II}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[ins].mountComponent() |
|-this.performInitialMount() |
|-this._renderValidatedComponent() |
|-instantiateReactComponent() _|_
|-ReactDOMComponent[6].mountComponent( |
transaction, // scr: -----> not of interest |
hostParent, // scr: -----> null |
hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins] |
context // scr: -----> not of interest |
) |
|
... // the content of this section lower half
|-_mountImageIntoNode() (HTML DOM specific)
markup, // scr: --> DOMLazyTree[ins] |
container, // scr: --> document.getElementById(‘root’)
wrapperInstance, // scr:----> same |
shouldReuseMarkup, // scr:--> same |
transaction, // scr: -------> same |
) _|_

Rendering Pass (Transaction)

To some extent, the sophisticated and efficient UI updating is what makes React React. But before we dive into the well-known mechanisms (virtual DOM and diffing algorithm) that empower the UI updating, we need to understand Transaction which transfers the control from the high-level API setState() to those underlying processing logic.

Files used in this section:

renderers/shared/utils/Transaction.js: defines the core Transaction class

renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js: defines ReactDefaultBatchingStrategyTransaction and its API wrapper ReactDefaultBatchingStrategy

renderers/shared/stack/reconciler/ReactUpdates.js: defines the enqueueUpdate() that uses ReactDefaultBatchingStrategy; defines flushBatchedUpdates() that is the entry point method of this section; defines ReactUpdatesFlushTransaction, one of the Transaction s we are going to discuss

shared/utils/PooledClass.js: defines PooledClass for enabling instance pooling

renderers/dom/client/ReactReconcileTransaction.js: defines ReactReconcileTransaction, another Transaction we are going to discuss

Unlike the previous sections that start from everyday APIs and move down the call stack. This section will take a bottom up approach.

So firstly, we look at the

Transaction the core class

The only de facto “public” method of this class is perform that also offers its core functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
/**
...
*
* @param {function} method Member of scope to call.
* @param {Object} scope Scope to invoke from.
* @param {Object?=} a Argument to pass to the method.
* @param {Object?=} b Argument to pass to the method.
* @param {Object?=} c Argument to pass to the method.
* @param {Object?=} d Argument to pass to the method.
* @param {Object?=} e Argument to pass to the method.
* @param {Object?=} f Argument to pass to the method.
*
* @return {*} Return value from `method`.
*/
perform: function<
A,
B,
C,
D,
E,
F,
G,
T: (a: A, b: B, c: C, d: D, e: E, f: F) => G,
>(method: T, scope: any, a: A, b: B, c: C, d: D, e: E, f: F): G {
/* eslint-enable space-before-function-paren */
...
var errorThrown;
var ret;
try {
this._isInTransaction = true;
...
// one of these calls threw.
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
...
try {
this.closeAll(0);
} catch (err) {}
} else {
...
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
...

TransactionImpl@renderers/shared/utils/Transaction.js

Besides the invocation of the callback method passed to it as the first argument, perform() simply 1) invokes initializeAll() before the callback and 2) closeAll() after.

Here the errorThrown is used to indicate an exception occurred within method.call(), in which case the logic jump directly to finally block before errorThrown can get a chance to be set to false.

Next we look at the implementation of the two methods that are invoked before and after perform(),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
...
initializeAll: function(startIndex: number): void {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
...
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize
? wrapper.initialize.call(this)
: null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},

...
closeAll: function(startIndex: number): void {
...// scr: sanity check
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
},
};

export type Transaction = typeof TransactionImpl;

TransactionImpl@renderers/shared/utils/Transaction.js

These two methods simply iterate this.transactionWrappers and call their initialize() and close() respectively.

The this.transactionWrappers is initialized in the de fecto constructor of Transaction with this.getTransactionWrappers():

1
2
3
4
5
6
7
8
9
10
11
12
13
...
reinitializeTransaction: function(): void {
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
...

TransactionImpl@renderers/shared/utils/Transaction.js

We will see what exactly are those this.transactionWrappers very soon.

The exception handling detail here is a bit interesting. Take initializeAll() as an instance. In the case that an exception occurs within initialize(), a finally block (instead of a catch) processes the initialize() of the rest of this.transactionWrappers (i.e., from i + 1 to transactionWrappers.length-1). Then the exception interrupts the for loop and the entire initializeAll() logic and processes all the way to the finally block within perform(), the initializeAll()’s caller, which effectively skips

1
ret = method.call(scope, a, b, c, d, e, f);

in the case of a exceptional initialization. At last, closeAll() is invoked within the same finally block to finalize the transaction.

Now we know what is a Transaction in its essence, but what is it used for? In order to answer this question, we take a Transaction instantiation as an example that is the transactional entry point of UI updating.

ReactDefaultBatchingStrategyTransaction

Firstly ReactDefaultBatchingStrategyTransaction is a subclass of Transaction that implements getTransactionWrappers():

1
2
3
4
5
6
7
8
9
...
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
...

ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

Next, TRANSACTION_WRAPPERS are the source of this.transactionWrappers that offers the pre (initialize()) and post (close()) functions for perform() used in the last section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
}; // scr: -----------------------------------------------------> 2)

var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
}; // scr: -----------------------------------------------------> 2)

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; // scr: -------------------------------> 2)

function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
} // scr: ------------------------------------------------------> 1)
...
// scr: ------------------------------------------------------> 3)
var transaction = new ReactDefaultBatchingStrategyTransaction();
...

ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

1) in the constructor of ReactDefaultBatchingStrategyTransaction the super class Transaction’s constructor gets called, which initializes this.transactionWrappers with FLUSH_BATCHED_UPDATES defined in 2)

2) defines the two wrapper and their respective initialize() and close(), which is used in the Transaction.initializeAll() and Transaction.closeAll() in the loops iterating FLUSH_BATCHED_UPDATES

3) defines ReactDefaultBatchingStrategyTransaction as a singleton.

Last we look at the public API offered by ReactDefaultBatchingStrategy that can be called from the outside world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,

batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

ReactDefaultBatchingStrategy.isBatchingUpdates = true;

// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) { // scr: --------> not applied here
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};

ReactDefaultBatchingStrategy@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

ReactDefaultBatchingStrategy is injected {section I *5} to ReactUpdates as batchingStrategy. And the ReactDefaultBatchingStrategy.batchedUpdates() is used by ReactUpdates.enqueueUpdate(), the underlying method of the UI updating entry point setState().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function enqueueUpdate(component) {
ensureInjected();

if (!batchingStrategy.isBatchingUpdates) { // scr: ----------> {a}
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}

// scr: -----------------------------------------------------> {b}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
// scr: this field is used for sanity check later
component._updateBatchNumber = updateBatchNumber + 1;
}
}

ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js

Here is a similar recursion trick as we saw in last section.

1) When the method is entered the first time, ReactDefaultBatchingStrategy.isBatchingUpdates is false, which triggers branch {a} that leads to ReactDefaultBatchingStrategy.batchedUpdates();

2) batchedUpdates() sets ReactDefaultBatchingStrategy.isBatchingUpdates to true, and initializes a transaction;

3) the callback argument of batchedUpdates is enqueueUpdate() itself, so enqueueUpdate will be entered again with transaction.perform() straight away. Note that the pre-methods (initialize()) of both wrappers are emptyFunction so nothing happens between the two times invocation of enqueueUpdate();

4) when enqueueUpdate() is entered the second time (within the context of the transaction just initialized), branch {b} is executed;

1
2
3
...
dirtyComponents.push(component);
...

5) after enqueueUpdate() returned post-method (close()) of FLUSH_BATCHED_UPDATES is called; This is the workhorse method that processes all the dirtyComponents marked in the previous step(s)

*8 we will come back to this FLUSH_BATCHED_UPDATES.close() and ReactUpdates.flushBatchedUpdates() in the next section

6) last, post-method (close()) of RESET_BATCHED_UPDATES is called, which sets ReactDefaultBatchingStrategy.isBatchingUpdates back to false and completes the circle.

It is important to note that any successive calls of enqueueUpdate() between 3) and 6) are supposed to be executed in the context of ReactDefaultBatchingStrategy.isBatchingUpdates:false, meaning, branch {b} will be taken in such case. So it’s like

1
2
3
4
5
->dirtyComponents.push(component);
->dirtyComponents.push(component);
->dirtyComponents.push(component);
...
----->ReactUpdates.flushBatchedUpdates

Figure 27.

Next let’s examine the other Transactions which, together with ReactDefaultBatchingStrategyTransaction, outline the UI updating processing logic.

We start with the ReactUpdates.flushBatchedUpdates() {last section *8}.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
var flushBatchedUpdates = function () {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) { // scr: not applied
...
}
}
};
...

ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js

As mentioned before, it processes all the dirtyComponents by initiating a ReactUpdatesFlushTransaction that will invoke runBatchedUpdates eventually.

PooledClass - maintain an instance pool

The two uncommon methods getPooled(), release() are inherited from PooledClass, which provides the instance pooling capacity:

a) if there is no allocated instance (in this case, ReactUpdatesFlushTransaction) in the pool, getPooled() creates a new one;

b) if instances exist in the pool, getPooled() simply returned the instance;

c) release() does not release the instance, instead, it simply put the instance back to the pool.

Like any other kinds of pools, the end purpose of this instance pooling is to reduce the overhead of superfluous resource (in this case, memory) allocation and destruction.

Back to our specific case above:

1
2
3
4
5
...
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
...

ReactUpdatesFlushTransaction instances are allocated only when the first time the while loop is executed. After that instances can be obtained through getPooled() from the pool directly.

This level of understanding of PooledClass is sufficient for the rest of this section. So feel free to fast travel to the the next section by ctrl-f “ReactUpdatesFlushTransaction”.

Now we look at its implementation:

1
2
3
4
5
6
7
8
9
var PooledClass = {
addPoolingTo: addPoolingTo, // scr: ------> 1)
oneArgumentPooler: (oneArgumentPooler: Pooler), // scr: ------> 2)
...// scr: not used
};

module.exports = PooledClass;

PooledClass@shared/utils/PooledClass.js

1) addPoolingTo() is a “public” method that adds the pooling functionality to a class;

2) oneArgumentPooler() is the getPooled() underlying implementation.

Next we look at addPoolingTo()‘s function body:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;
...

var addPoolingTo = function<T>(
CopyConstructor: Class<T>,
pooler: Pooler,
): Class<T> & {
getPooled(): /* arguments of the constructor */ T,
release(): void,
} {
var NewKlass = (CopyConstructor: any);
NewKlass.instancePool = []; // scr: -------------> 1)
NewKlass.getPooled = pooler || DEFAULT_POOLER; // scr: -------> 2)
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE; // scr: -------------> 3)
}
NewKlass.release = standardReleaser; // scr: -------------> 4)
return NewKlass;
};
...

addPoolingTo@shared/utils/PooledClass.js

1) instancePool is the pool;

2) attach DEFAULT_POOLER (a.k.a., oneArgumentPooler) to the getPooled();

3) set poolSize to 10;

4) attach standardReleaser() to the release().

And this is how getPooled() and release() are implemented:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var oneArgumentPooler = function(copyFieldsFrom) {
var Klass = this;
if (Klass.instancePool.length) { // scr: -----------> 1)
var instance = Klass.instancePool.pop(); // scr: -----------> 1)
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom); // scr: ----------------> 2)
}
};

oneArgumentPooler@shared/utils/PooledClass.js

...

var standardReleaser = function(instance) {
var Klass = this;
...
instance.destructor();
if (Klass.instancePool.length < Klass.poolSize) { // scr: ----> 3)
Klass.instancePool.push(instance); // scr: ----> 3)
}
};

standardReleaser@shared/utils/PooledClass.js

1) corresponds to b), in which

1
Klass.call(instance, copyFieldsFrom);

invokes Klass constructor with the designated parameters (copyFieldsFrom) to initialize the pooling enabled class; and

2) corresponds to a); and

3) corresponds to c).

Last, we look at how addPoolingTo() is used from the outside (ReactUpdatesFlushTransaction):

1
2
3
4
5
...
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
...

ReactUpdatesFlushTransaction@renderers/shared/stack/reconciler/ReactUpdates.js

ReactUpdatesFlushTransaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
function ReactUpdatesFlushTransaction() {
this.reinitializeTransaction();
this.dirtyComponentsLength = null;
this.callbackQueue = CallbackQueue.getPooled();
this.reconcileTransaction =
ReactUpdates.ReactReconcileTransaction.getPooled( // scr: ---> 2)
/* useCreateElement */ true,
);
}

// scr: --------------------------------------------------------> 1)
Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS; // scr: -----------------------> 3)
},

destructor: function() {
this.dirtyComponentsLength = null;
CallbackQueue.release(this.callbackQueue);
this.callbackQueue = null;
ReactUpdates.ReactReconcileTransaction.release( // scr: ----> 2)
this.reconcileTransaction
);

this.reconcileTransaction = null;
},

perform: function(method, scope, a) {
return Transaction.perform.call(
this,
this.reconcileTransaction.perform, // scr: ---------------> 2)
this.reconcileTransaction,
method,
scope,
a,
);
},
});
...

ReactUpdatesFlushTransaction@renderers/shared/stack/reconciler/ReactUpdates.js

1) it is another instantiation of Transaction that overrides the perform() method;

2) instead of calling ReactUpdate.runBatchedUpdates (the callback) directly, the overridden ReactUpdatesFlushTransaction.perform() nest calls another Transaction (ReactReconcileTransaction)’s perform() method and pass method (i.e., ReactUpdate.runBatchedUpdates()) as its callback. Note that ReactReconcileTransaction is also pooling enabled.

3) TRANSACTION_WRAPPERS defines its pre and post-functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates(); // scr: ----------------------> a)
} else {
dirtyComponents.length = 0; // scr: ----------------------> b)
}
},
};

var UPDATE_QUEUEING = { // scr: ------> we omit this wrapper for now
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
},
};

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
...

ReactUpdatesFlushTransaction@renderers/shared/stack/reconciler/ReactUpdates.js

in which, NESTED_UPDATES‘s initialize() 1.5) stores the number of dirtyComponents; its close() 3) check if the number has changed. If they are different {a} the flushBatchedUpdates() is called to reiterate the process; or {b} it set dirtyComponents.length back to 0, and returns back to the upper level Transaction, ReactDefaultBatchingStrategyTransaction {last section}.

I will not examine the CallbackQueue related operations (in UPDATE_QUEUEING) and will leave them for later articles when discussing component’s life cycle. *9

To recap:

Figure 28.

ReactReconcileTransaction

It is another Transaction and nothing is out of ordinary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var Mixin = {
/**
* @see Transaction
* @abstract
* @final
* @return {array<object>} List of operation wrap procedures.
* TODO: convert to array<TransactionWrapper>
*/
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS; // scr: -----------------------> 1)
},
// scr: ---------------------> we omit all CallbackQueue s for now
getReactMountReady: function() {
return this.reactMountReady;
},

// scr: ---------------------> we omit all CallbackQueue s for now
getUpdateQueue: function() {
return ReactUpdateQueue;
},

checkpoint: function() { // scr: -----------------------> not used
// reactMountReady is the our only stateful wrapper
return this.reactMountReady.checkpoint();
},

rollback: function(checkpoint) { // scr: ---------------> not used
this.reactMountReady.rollback(checkpoint);
},

// scr: ------------------------------------> for instance pooling
destructor: function() {
CallbackQueue.release(this.reactMountReady);
this.reactMountReady = null;
},
};

Object.assign(ReactReconcileTransaction.prototype, Transaction, Mixin);

// scr: --------------------------------------------------------> 2)
PooledClass.addPoolingTo(ReactReconcileTransaction);

module.exports = ReactReconcileTransaction;

ReactReconcileTransaction@renderers/dom/client/ReactReconcileTransaction.js

1) Its wrappers are defined in TRANSACTION_WRAPPERS;

2) as mentioned before, this class is pooling enabled.

Next we look at its three wrapper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* Ensures that, when possible, the selection range (currently selected text
* input) is not disturbed by performing the transaction.
*/
var SELECTION_RESTORATION = {
/**
* @return {Selection} Selection information.
*/
initialize: ReactInputSelection.getSelectionInformation,
/**
* @param {Selection} sel Selection information returned from `initialize`.
*/
close: ReactInputSelection.restoreSelection,
};

/**
* Suppresses events (blur/focus) that could be inadvertently dispatched due to
* high level DOM manipulations (like temporarily removing a text input from the
* DOM).
*/
var EVENT_SUPPRESSION = {
/**
* @return {boolean} The enabled status of `ReactBrowserEventEmitter` before
* the reconciliation.
*/
initialize: function() {
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
return currentlyEnabled;
},

/**
* @param {boolean} previouslyEnabled Enabled status of
* `ReactBrowserEventEmitter` before the reconciliation occurred. `close`
* restores the previous value.
*/
close: function(previouslyEnabled) {
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
},
};

/**
* Provides a queue for collecting `componentDidMount` and
* `componentDidUpdate` callbacks during the transaction.
*/
var ON_DOM_READY_QUEUEING = {
/**
* Initializes the internal `onDOMReady` queue.
*/
initialize: function() {
this.reactMountReady.reset();
},

/**
* After DOM is flushed, invoke all registered `onDOMReady` callbacks.
*/
close: function() {
this.reactMountReady.notifyAll();
},
};

var TRANSACTION_WRAPPERS = [
SELECTION_RESTORATION,
EVENT_SUPPRESSION,
ON_DOM_READY_QUEUEING,
];

ReactReconcileTransaction@renderers/dom/client/ReactReconcileTransaction.js

The comment is quite clear here:

SELECTION_RESTORATION is for storing the focus state of text fields before the UI updating (initialize()), and it restores the state after (close());

EVENT_SUPPRESSION is for storing the toggle state for enabling event, and disable event temporarily before (initialize()), and it restores the state after UI updating (close()).

Again, I will not examine the CallbackQueue related operations (in ON_DOM_READY_QUEUEING) here and will leave them for later articles when discussing component’s life cycle. *10

It is important to note that ReactReconcileTransaction relies on the default Transaction.perform(). Its callback is the ReactUpdate.runBatchedUpdates which is passed all the way down to this level.

ctrl-f “runBatchedUpdates” to examine its route.

And this ReactUpdate.runBatchedUpdates will lead to the content of my next section.

to recap:

Figure 29.

Conclusion

Figure 30.

Rendering Pass (Individual Element)

UI updating, in its essential, is data change. React offers a straightforward and intuitive way to program front-end Apps as most moving parts are converged in the form of states, and most UI tasks can be done with a single

Figure 31.

…, I mean, a single method, setState(). In this article, we are going unfold the setState() implementation, and peek inside the diffing algorithm by mutating an individual DOM element.

Firstly Let’s extend an example from {section III}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class App extends Component {
constructor(props) {
super(props);
this.state = {
desc: 'start',
color: 'blue'
};

this.timer = setTimeout(
() => this.tick(),
5000
);
}

tick() {
this.setState({
desc: 'end',
color: 'green'
});
}

render() {
return (
<div className="App">
<div className="App-header">
<img src="main.jpg" className="App-logo" alt="logo" />
<h1> "Welcom to React" </h1>
</div>
<p className="App-intro" style={{color: this.state.color}}>
{ this.state.desc }
</p>
</div>
);
}
}

export default App;

Compared to the App component used in {section III}, the new version adds style prop to <p> node, and setState()s desc to 'end' and color to 'green' 5 seconds after the component is constructed.

The instantiating of App has been discussed in {section III}.

ctl-f “setState”
In the same article, I also mentioned ReactInstanceMap, a back link (from the external ReactComponent instance) to the internal ReactCompositeComponent[ins], which will be used very soon.

Here I paste the data structure as a reminder.

Figure 32.

Before transactions

We start from the setState() method body:

1
2
3
4
5
6
7
8
9
10
11
12
ReactComponent.prototype.setState = function (
partialState,
callback
) {
// scr: ---> sanity check
this.updater.enqueueSetState(this, partialState);
if (callback) {
// scr: ---> no callbak
}
};

ReactComponent@isomorphic/modern/class/ReactBaseClasses.js

Yes, setState() is inherited from ReactComponent.

But wait, what is this.updater? isn’t it set to ReactNoopUpdateQueue in the constructor, and is a no-op? In fact, I believe with the understanding of Transaction(s) and instance pooling {section VI}, if you trace back from the aforementioned ReactComponent instantiating {section III}, you will be able to find out the origin of this.updater very easily.

I will leave this question open so we can move faster to the core part —virtual DOM and diffing algorithm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enqueueSetState: function (publicInstance, partialState) {
// scr: DEV code

// scr: ------------------------------------------------------> 1)
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

if (!internalInstance) {
return;
}

// scr: ------------------------------------------------------> 2)
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);

// scr: ------------------------------------------------------> 3)
enqueueUpdate(internalInstance);
},

ReactUpdateQueue@renderers/shared/stack/reconciler/ReactUpdateQueue.js

1) this is the method that obtains the internal ReactCompositeComponent[ins] from the back link ReactInstanceMap;

1
2
3
4
5
6
7
8
9
10
11
12
function getInternalInstanceReadyForUpdate(
publicInstance,
callerName
) {
var internalInstance = ReactInstanceMap.get(publicInstance);

... // scr: DEV code

return internalInstance;
}

getInternalInstanceReadyForUpdate@renderers/shared/stack/reconciler/ReactUpdateQueue.js

2) attach an array (_pendingStateQueue) to ReactCompositeComponent[ins], and push the changed state {desc:'end',color:'green'} into it;

3) start the Transaction(s) {section V},

1
2
3
4
5
6
7
...
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
...

enqueueUpdate@renderers/shared/stack/reconciler/ReactUpdateQueue.js

The call stack so far:

1
2
3
4
5
6
|-ReactComponent.setState()
|-ReactUpdateQueue.enqueueSetState()
|-getInternalInstanceReadyForUpdate()
|-enqueueUpdate()
|-ReactUpdates.enqueueUpdate()
|~~~

Here I also paste the transaction related call graph as a reminder.

Figure 33.

In transactions

The first stop after the Transaction(s) are fully initialized is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;

// scr: -----------------------------------> sanity check
...

dirtyComponents.sort(mountOrderComparator);

updateBatchNumber++;

for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];

var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;

// scr: ------------------------------> logging
...

ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);

// scr: ------------------------------> logging
if (callbacks) { // scr: -------------> no callbacks
...
}
}
}

ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js

This time we have one dirtyComponents, ReactCompositeComponent[ins] which is the first parameter of ReactReconciler.performUpdateIfNecessary().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
performUpdateIfNecessary: function (
internalInstance,
transaction,
updateBatchNumber
) {
// scr: DEV code
...

internalInstance.performUpdateIfNecessary(transaction);

// scr: DEV code
...
}

ReactReconciler@renderers/shared/stack/reconciler/ReactUpdates.js

Like most of the other methods in ReactReconciler class, ReactReconciler.performUpdateIfNecessary() will call the component’s same method, ReactCompositeComponent.performUpdateIfNecessary()

it’s like a polymorphism in a more explicit way

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// scr: -----------> condition not applied
...
} else if (
this._pendingStateQueue !== null ||
this._pendingForceUpdate
) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
} else {
// scr: -----------> condition not applied
...
}
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

It in turn calls ReactCompositeComponent[ins].updateComponent(). Note that _pendingStateQueue is set right before the logic enters the Transaction context.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext,
) {
var inst = this._instance; // scr: ---------------------------> 1)
// scr: sanity check and code that is not applicable this time
...

// scr: ------------------------------------------------------> 2)
var nextState = this._processPendingState(nextProps, nextContext);

var shouldUpdate = true;

if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) { // scr: ------------------> 3)
shouldUpdate = inst.shouldComponentUpdate(
nextProps,
nextState,
nextContext,
);
} else {
if (this._compositeType === CompositeTypes.PureClass) {
// scr: ---------------> it is ImpureClass, not applicable
...
}
}
}

this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate( // scr: --------------------> 4)
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext,
);
} else {
// scr: code that is not applicable this time
...
}
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

1) obtain the external ReactComponent instance (App) from ReactCompositeComponent[ins]._instance (Figure 32);

2) merge the partial state in ReactCompositeComponent[ins]._pendingStateQueue ({desc:'end',color:'green'}) and existing states using Object.assign();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_processPendingState: function(props, context) {
// scr: -------> obtain the App (Figure 32)
var inst = this._instance;
var queue = this._pendingStateQueue;
// scr: code that is not applicable this time
...

var nextState = Object.assign({}, replace ? queue[0] : inst.state);

for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
Object.assign(
nextState,
typeof partial === 'function'
? partial.call(inst, nextState, props, context)
: partial,
);
}

return nextState;
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

3) this is the lifecycle function that is provided to the developers to avoid reconciliation (the following processing logic) from being executed in case setState() does not change the critical states;

Most likely you do not need this function

4) enter the next stop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
_performComponentUpdate: function(
nextElement,
nextProps,
nextState,
nextContext,
transaction,
unmaskedContext,
) {
var inst = this._instance; // scr: (Figure 32)
// scr: code that is not applicable this time
...
// scr: invoke App's life cycle method if defined
if (inst.componentWillUpdate) {
inst.componentWillUpdate(nextProps, nextState, nextContext);
}
// scr: code that is not applicable this time
...
inst.state = nextState;
...
this._updateRenderedComponent(transaction, unmaskedContext);
// scr: queue App's life cycle method if defined
if (hasComponentDidUpdate) {
...
}
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

It simply sets the App’s state to the newly merged one. And calls this._updateRenderedComponent() which is the entry point of the diffing algorithm.

The call stack so far,

1
2
3
4
5
6
7
8
9
10
...
|~~~
|-runBatchedUpdates()
|-performUpdateIfNecessary()
|-ReactCompositeComponent[ins].performUpdateIfNecessary()
|-this.updateComponent()
|-this._processPendingState()
|-this._performComponentUpdate() ___
|-this._updateRenderedComponent() |
... diffing

Then the logic processes to the diffing algorithm.

Virtual DOM Diffing

Before we start examining the Diffing algorithm, we better have a consent about what exactly are virtual DOMs, as the term did not appear in the code base.

Here I paste an image from {section IV} as a reminder:

Figure 34.

The ReactElements are the virtual DOMs we are going to agree on. {section III, section IV} also discussed how the virtual DOM tree are initially established.

In MVC terms ReactElements are modals which contain only data. On the other hand, ReactDOMComponents are controllers that offer actionable methods.

The figure above gives the old virtual DOM tree that is generated in {section III}.

ctl-f “in _renderValidatedComponent()”

This step will generate a new one with ReactCompositeComponent[ins]._renderValidatedComponent() based on the changed states, for the purpose of diffing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
_updateRenderedComponent: function (transaction, context) {
var prevComponentInstance = this._renderedComponent; // scr: -> 1)
// scr: ------------------------------------------------------> 2)
var prevRenderedElement = prevComponentInstance._currentElement;
// scr: create a new DOM tree
var nextRenderedElement = this._renderValidatedComponent();
var debugID = 0;
// scr: DEV code
...
if (shouldUpdateReactComponent( // scr: ----------------------> 3)
prevRenderedElement,
nextRenderedElement)
) {
ReactReconciler.receiveComponent( // scr: ------------------> 5)
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context)
);
} else { // scr: ---------------------------------------------> 4)
// scr: code that is not applicable this time
...
}
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

1) obtain ReactDOMComponent[6] through `ReactCompositeComponent[ins] (Figure 34);

2) cascading call of React.createElement() in App[ins].render() to create the new DOM tree {section III}, in which the only different DOM node is:

Figure 35.

3) the first comparison of diffing algorithm is between types of the old and new root elements;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;

if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}

var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
}
}

shouldUpdateReactComponent@renderers/shared/shared/shouldUpdateReactComponent.js

4) if they are not the same, build the new tree from scratch — the component mounting process is similar to that discussed in {post five};

whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch

5) if the a the same so, start the DOM updating process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
receiveComponent: function (nextElement, transaction, context) {
var prevElement = this._currentElement;
this._currentElement = nextElement;
this.updateComponent(transaction,
prevElement,
nextElement,
context);
},

updateComponent: function(
transaction,
prevElement,
nextElement,
context
) {
var lastProps = prevElement.props;
var nextProps = this._currentElement.props;
// scr: code that is not applicable this time
...
// scr: ------------------------------------------------------> 1)
this._updateDOMProperties(lastProps, nextProps, transaction);
// scr: ------------------------------------------------------> 2)
this._updateDOMChildren(lastProps, nextProps, transaction, context);
// scr: code that is not applicable this time
...
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

1) get the props from the old virtual DOM (lastProps) and the newly created one (nextProps);

2) ReactDOMComponent._updateDOMProperties() checks the old and new versions of a DOM’s props, and calls CSSPropertyOperations.setValueForStyles() to update the DOM if different;

3) ReactDOMComponent._updateDOMChildren() checks the old and new versions of a DOM’s content (text, inner HTML), and calls ReactDOMComponent.updateTextContent() to update the DOM’s (text) content if different.

The static call stack,

1
2
3
4
5
6
7
8
9
10
...                                                            ___
ReactReconciler.receiveComponent() <----------------| |
|-ReactDOMComponent.receiveComponent() | |
|-this.updateComponent() | |
|-this._updateDOMProperties() | diffing
|-CSSPropertyOperations.setValueForStyles() | |
|-this._updateDOMChildren() | |
|-this.updateTextContent() | |
|-recursing children (not the focus this time) --| |
---

By observing the static call stack, it is not hard to deduce the how the recursion works.

1) one iteration of this recursion updates the properties of one virtual DOM;

2) ReactDOMComponent.updateDOMChildren() is also responsible to go through the current virtual DOM’s direct children and invoke the next iteration for each of them.

note that sub DOM recursing is not the focus of this section

I collapse some method calls in the above call stack,

1
2
3
4
5
6
|-ReactReconciler.receiveComponent()
|-ReactDOMComponent[n].receiveComponent()
|-this.updateComponent()
=>

|-ReactDOMComponent[n].updateComponent()

and draw the call stack in action for clarity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
|-ReactDOMComponent[6].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren
|-recursing children (not the focus this time...)
|-ReactDOMComponent[4].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren
|-recursing children (not the focus this time...)
|-ReactDOMComponent[2].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren // scr: ----> same
|-ReactDOMComponent[3].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren // scr: ----> same
|-ReactDOMComponent[5].updateComponent()
|-this._updateDOMProperties()
|-CSSPropertyOperations.setValueForStyles()
|-this._updateDOMChildren
|-this.updateTextContent()

ReactDOMComponent._updateDOMProperties() —check if a DOM changed

This is the overlooked method in {section II *6}
In this article we focus on only STYLE updating related code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
_updateDOMProperties: function(lastProps, nextProps, transaction) {
var propKey;
var styleName;
var styleUpdates;
// scr: --------------------------------------------------------> 1)
for (propKey in lastProps) {
if (
nextProps.hasOwnProperty(propKey) ||
!lastProps.hasOwnProperty(propKey) ||
lastProps[propKey] == null
) {
continue;
}
if (propKey === STYLE) {
var lastStyle = this._previousStyleCopy;
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
this._previousStyleCopy = null;
} else if ... {
// scr: not the focus this time
...
}
}

// scr: --------------------------------------------------> end 1)
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = propKey === STYLE
? this._previousStyleCopy
: lastProps != null ? lastProps[propKey] : undefined;
if (
!nextProps.hasOwnProperty(propKey) ||
nextProp === lastProp ||
(nextProp == null && lastProp == null)
) {
continue;
}
if (propKey === STYLE) {
if (nextProp) {
// scr: DEV code
...
// scr: -------------------------------------------------> 2)
nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
} else {
this._previousStyleCopy = null;
}
if (lastProp) { // scr: ----------------------------------> 3)
// scr: the comment applies here -----------------------> a)
// Unset styles on `lastProp` but not on `nextProp`.

for (styleName in lastProp) {
if (
lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))
) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
// scr: the comment applies here -----------------------> b)
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
if (
nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]
) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = nextProp[styleName];
}
}
} else { // scr: -----------------------------------------> 4)
// Relies on `updateStylesByID` not mutating `styleUpdates`.
styleUpdates = nextProp;
}
} else if (...) {
// scr: DEV code
...
}
}
if (styleUpdates) { // scr: ----------------------------------> 5)
CSSPropertyOperations.setValueForStyles(
getNode(this),
styleUpdates,
this,
);
}
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

1) if the new props do not contain “style” at all,

1
2
3
4
5
...
if (nextProps.hasOwnProperty(propKey) ||...) {
continue;
} // scr: else, do something
...

mark all the existing style entries as ‘remove’, note that existing styles are stored in this._previousStyleCopy in step 2);

2) copy nextProp (current styles) to this._previousStyleCopy;

3) if there are existing styles,

1
2
3
4
5
var lastProp = propKey === STYLE
? this._previousStyleCopy
...
if (lastProp) {
...

update by a) marking existing style entries that are not in nextProp as ‘remove’ and b) marking style entries in nextProp as ‘add’ if it is different from the existing entry on the same key;

4) if not, simply mark all the styles in nextProp as ‘add’;

5) conduct the real DOM operations. Note that getNode() is an alias to ReactDOMComponentTree.getNodeFromInstance() that uses ReactDOMComponent._hostNode to get the associated DOM element {section II}.

ctl-f “ReactDOMComponent[ins]._hostNode”

CSSPropertyOperations.setValueForStyles() — update props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
setValueForStyles: function(node, styles, component) {
var style = node.style;
for (var styleName in styles) {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
// scr: DEV code or code that is not applicable
...

if (isCustomProperty) {
...
} else if (styleValue) {
style[styleName] = styleValue;
} else {
code that is not applicable this time
...
}
}
},

CSSPropertyOperations@renderers/dom/shared/CSSPropertyOperations.js

Here the only line that is applicable here is style[styleName] = styleValue; that set the node.style with styles marked in the previous method.

As a result, Node.style[‘color’] = ‘red’.

_updateDOMChildren —check if a DOM’s content changed (and recurse its children)

We omit the dangerouslySetInnerHTML related code and focus only on hot paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
_updateDOMChildren: function(
lastProps,
nextProps,
transaction,
context
) {
var lastContent = CONTENT_TYPES[typeof lastProps.children]
? lastProps.children
: null;
var nextContent = CONTENT_TYPES[typeof nextProps.children]
? nextProps.children
: null;
// scr: code that is not applicable
...
// Note the use of `!=` which checks for null or undefined.
// scr: used by recursing children, to be continued...
var lastChildren = lastContent != null ? null : lastProps.children;
var nextChildren = nextContent != null ? null : nextProps.children;
// scr: code that is not applicable
...
if (lastChildren != null && nextChildren == null) {
// scr: recursing children, to be continued...
this.updateChildren(null, transaction, context);
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
// scr: DEV code and code that is not applicable
...
}
if (nextContent != null) {
if (lastContent !== nextContent) {
this.updateTextContent('' + nextContent);
// scr: DEV code
...
}
} else if (nextHtml != null) {
// scr: code that is not applicable
...
} else if (nextChildren != null) {
// scr: DEV code
...
// scr: recursing children, to be continued...
this.updateChildren(nextChildren, transaction, context);
}
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

The only line that is applicable here is

1
this.updateTextContent(‘’ + nextContent);

ReactDOMComponent.updateTextContent() — update content

Presumably ReactDOMComponent.updateTextContent() is used to set the text from 'start' to 'end'. But the call stack of this process is a bit too deep for this simple operation,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
updateTextContent: function(nextContent) {
var prevChildren = this._renderedChildren;
// Remove any rendered children. scr: -------> the comment applies
ReactChildReconciler.unmountChildren(prevChildren, false);
for (var name in prevChildren) {
// scr: sanity check
...
}

// Set new text content. scr: ---------------> the comment applies
var updates = [makeTextContent(nextContent)];
processQueue(this, updates);
},

function processQueue(inst, updateQueue) {
ReactComponentEnvironment.processChildrenUpdates(inst, updateQueue);
}

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

Here ReactComponentBrowserEnvironment is injected as ReactComponentEnvironment.

1
2
3
4
5
6
dangerouslyProcessChildrenUpdates: function(parentInst, updates) {
var node = ReactDOMComponentTree.getNodeFromInstance(parentInst);
DOMChildrenOperations.processUpdates(node, updates);
},

ReactDOMIDOperations@renderers/dom/client/ReactDOMIDOperations.js

The ReactDOMComponentTree.getNodeFromInstance() method is discussed in the previous section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
processUpdates: function(parentNode, updates) {
// scr: DEV code
...
for (var k = 0; k < updates.length; k++) {
var update = updates[k];
switch (update.type) {
// scr: code that is not applicable
...
case 'TEXT_CONTENT':
setTextContent(parentNode, update.content);
// scr: DEV code
...
break;
...

DOMChildrenOperations@renderers/dom/client/utils/DOMChildrenOperations.js

As expected, the last card in this stack is setTextContent() which sets Node.textContent directly. This method is covered in {section IV} so I will not repeat its implementation.

The sub call stack of ReactDOMComponent.updateTextContent() and the ‘end’ result of it:

1
2
3
4
5
6
7
8
|-ReactDOMComponent.updateTextContent()
|-processQueue()
|-ReactComponentEnvironment.processChildrenUpdates()
|=ReactDOMIDOperations.dangerouslyProcessChildrenUpdates()
|-ReactDOMComponentTree.getNodeFromInstance()
|-DOMChildrenOperations.processUpdates()
|-setTextContent()
|-Node.textContent = 'end'

In the next section we are going to further investigate the diffing algorithm by observing the mutation of DOM trees, which also concludes this series (for a period of time). I hope you will feel more

Figure 36.

next time when using setState().

Rendering Pass (Element Tree)

Next let’s use two examples to examine the diffing algorithm more in depth. More specific, we look at how the algorithm deals with a mutating DOM tree.

N.b., the examples used in this article are derived from the official document which also provides a high level description of the diffing algorithm. You might want to read it first if the topic does not seem very familiar.

Example 1., diffing of key-less nodes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class App extends Component {
constructor(props) {
super(props);
this.state = {
data : ['one', 'two'],
};

this.timer = setInterval(
() => this.tick(),
5000
);
}

tick() {
this.setState({
data: ['new', 'one', 'two'],
});
}

render() {
return (
<ul>
{
this.state.data.map(function(val, i) {
return <li>{ val }</li>;
})
}
</ul>
);
}
}

export default App;

the babeled version of render(),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
render() {
return React.createElement(
'ul',
null,
this.state.data.map(function (val, i) {
return React.createElement(
'li',
null,
' ',
val,
' '
);
})
);
}

The old & new virtual DOM tree

We know the result virtual DOM tree of the render() method is {section III} (nested calling of React.createElement())

Figure 37.

We ignore the ReactElement’s corresponding controllers (i.e., ReactDOMComponent) for simplicity.

The figure above gives the old virtual DOM tree that is generated by the initial rendering. As in {last post}, a setState() is fired after 5 seconds, which initiates the updating process,

Figure 38.

With this data structure in mind, we skip the logic process (mostly, before transaction) that is identical to {last post}, and move directly to the diffing algorithm,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
_updateRenderedComponent: function (transaction, context) {
var prevComponentInstance = this._renderedComponent; // scr: -> 1)

// scr: ------------------------------------------------------> 2)
var prevRenderedElement = prevComponentInstance._currentElement;

// scr: create a new DOM tree
var nextRenderedElement = this._renderValidatedComponent();

var debugID = 0;

// scr: DEV code
...

if (shouldUpdateReactComponent( // scr: ----------------------> 3)
prevRenderedElement,
nextRenderedElement)
) {
ReactReconciler.receiveComponent( // scr: ------------------> 5)
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context)
);
} else { // scr: ---------------------------------------------> 4)
// scr: code that is not applicable this time
...
}
},

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

The steps 1–5) are also identical to {last post}.

, which starts by creating the new DOM tree (Figure 38) with ReactCompositeComponent._renderValidatedComponent(). {section III}

Root nodes are the identical, so “diff” their direct children

Since the types of ReactElement[1] are the same (“ul”), the logic goes to 5) as in {last post}.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
receiveComponent: function (nextElement, transaction, context) {
var prevElement = this._currentElement;
this._currentElement = nextElement;
this.updateComponent(transaction,
prevElement,
nextElement,
context);
},

updateComponent: function(
transaction,
prevElement,
nextElement,
context
) {
var lastProps = prevElement.props;
var nextProps = this._currentElement.props;

// scr: code that is not applicable this time
...

// scr: ------------------------------------------------------> 1)
this._updateDOMProperties(lastProps, nextProps, transaction);

// scr: ------------------------------------------------------> 2)
this._updateDOMChildren(lastProps, nextProps, transaction, context);

// scr: code that is not applicable this time
...
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

in {last post} step 1) updates a DOM node properties; and 2) updates its content.

But for the the root node (ReactElement[1]) the only purpose of the whole ReactDOMComponent.updateComponent() method call is to recurse and update ReactElement[1]’s direct children because neither the node’s properties and its content are changed.

I also extend the static call stack from {last post} as a lead:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...                                                            ___
ReactReconciler.receiveComponent() <----------------| |
|-ReactDOMComponent.receiveComponent() | |
|-this.updateComponent() | |
|-this._updateDOMProperties() | |
|-CSSPropertyOperations.setValueForStyles() | |
|-this._updateDOMChildren() | |
|-this.updateTextContent() | diffing
|-this._updateDOMChildren() (the focus this time)| |
|-this.updateChildren() | |
|=this._updateChildren() | |
|-this._reconcilerUpdateChildren() | |
|-this.flattenChildren() | |
|-ReactChildReconciler.updateChildren() ---| |
---

As mentioned before, the recursing starts from ReactDOMComponent._updateDOMChildren(). In the following sections, we will follow the hierarchy, one function a time, and go for the bottom of the stack.

Recursing children elements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
_updateDOMChildren: function (
lastProps, nextProps, transaction, context
) {
// scr: code for content updating
...
var nextChildren = nextContent != null ? null : nextProps.children;

if (lastChildren != null && nextChildren == null) { // scr: --> 1)
this.updateChildren(null, transaction, context);
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
// scr: code for content updating
...
}

if (nextContent != null) {
if (lastContent !== nextContent) {
// scr: code for content updating
...
} else if (nextHtml != null) {
// scr: code for content updating
...
} else if (nextChildren != null) {
// scr: DEV code
...
// scr: --------------------------------------------------> 2)
this.updateChildren(nextChildren, transaction, context);
}
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

I fold up the content updating related code so we can focus on DOM children recursing

1) remove the children only when necessary (lastChildren != null && nextChildren == null);

2) start the recursing.

ReactMultiChild.updateChildren() I — The actual work horse

After the methods that are either alias or those with very little (preprocessing) operations, we come to the work horse that I) recurses virtual DOM children, compares the new/old versions of them and modifies ReactDOMComponent’s accordingly (we name them virtual DOM operations for simplicity); and II) commits the operations to real DOMs.

the role of this ReactMultiChild.updateChildren() is similar to that of mountComponentIntoNode() in initial rendering {section I}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
updateChildren: function (
nextNestedChildrenElements,
transaction,
context
) {
// Hook used by React ART
this._updateChildren(nextNestedChildrenElements, transaction, context);
},

_updateChildren: function (
nextNestedChildrenElements,
transaction,
context
) {
var prevChildren = this._renderedChildren;
var removedNodes = {};
var mountImages = [];
var nextChildren = this._reconcilerUpdateChildren( // scr: ---> I)
prevChildren, // scr: ------------------> i)
nextNestedChildrenElements, // scr: ----> ii)
mountImages,
removedNodes,
transaction,
context
);

if (!nextChildren && !prevChildren) {
return;
}

// scr: -----------------------------------------------------> II)
var updates = null;
var name;
// `nextIndex` will increment for each child in `nextChildren`, but
// `lastIndex` will be the last index visited in `prevChildren`.
var nextIndex = 0;
var lastIndex = 0;

// `nextMountIndex` will increment for each newly mounted child.
var nextMountIndex = 0;
var lastPlacedNode = null;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}

var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
prevChild._mountIndex = nextIndex;
} else {
if (prevChild) {
// Update `lastIndex` before `_mountIndex` gets unset by unmounting.
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
// The `removedNodes` loop below will actually remove the child.
}

// The child must be instantiated before it's mounted.
updates = enqueue(updates, this._mountChildAtIndex(nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context));
nextMountIndex++;
}

nextIndex++;
lastPlacedNode = ReactReconciler.getHostNode(nextChild);
}

// Remove children that are no longer present.
for (name in removedNodes) {
if (removedNodes.hasOwnProperty(name)) {
updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
}
}

if (updates) {
processQueue(this, updates);
}
this._renderedChildren = nextChildren;

// scr: DEV code
...

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

We firstly look at the virtual DOM operations, I). Note that the two input parameters of the responsible method ReactDOMComponent._reconcilerUpdateChildren() are i) prevChildren, i.e., ReactDOMComponent._renderedChildren which is set to an object of its sub-ReactDOMComponents in initial rendering {section IV}; and ii) nextNestedChildrenElements, i.e., nextProps.children passed from ReactDOMComponent._updateDOMChildren().

ReactDOMComponent._reconcilerUpdateChildren() — Virtual DOM operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
_reconcilerUpdateChildren: function (
prevChildren,
nextNestedChildrenElements,
mountImages,
removedNodes,
transaction,
context
) {
var nextChildren;
var selfDebugID = 0;
// scr: DEV code
...

nextChildren = flattenChildren( // scr: -----------------> 1)
nextNestedChildrenElements,
selfDebugID);

ReactChildReconciler.updateChildren( // scr: -----------------> 2)
prevChildren,
nextChildren,
mountImages,
removedNodes,
transaction,
this,
this._hostContainerInfo,
context, selfDebugID);

return nextChildren;
},

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

Before 2) the virtual DOM can be traversed and compared, this method 1) calls

flattenChildren() — converts ReactElement array into an object (map)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function flattenChildren(children, selfDebugID) {
if (children == null) {
return children;
}
var result = {};

// scr: DEV code
...

{
traverseAllChildren(children, flattenSingleChildIntoContext, result);
}

return result;
}

flattenChildren@shared/utils/flattenChildren.js

Here we need to pay attention to the callback passed to traverseAllChildren()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function flattenSingleChildIntoContext(
traverseContext,
child,
name,
selfDebugID
) {
// We found a component instance.
if (traverseContext && typeof traverseContext === 'object') {
var result = traverseContext;
var keyUnique = result[name] === undefined;

// scr: DEV code
...

if (keyUnique && child != null) {
result[name] = child;
}
}
}

flattenSingleChildIntoContext@shared/utils/flattenChildren.js

, which set individual ReactElement with its associated key (name) in an object (map). Next we look at the traverseAllChildren() method body, to see in particular how the keys are generated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
...
var SEPARATOR = '.';
...

function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}

return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

traverseAllChildren@shared/utils/traverseAllChildren.js

function traverseAllChildrenImpl(
children,
nameSoFar, // scr: -------- ''
callback,
traverseContext
) {
var type = typeof children;

if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}

if (children === null || type === 'string' || type === 'number' ||
type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) {
callback(traverseContext, children,
// If it's the only child, treat the name as if it was wrapped in an array
// so that it's consistent if the number of children grows.
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
return 1;
}

var child;
var nextName;
var subtreeCount = 0; // Count of children found in the current subtree.
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
}
} else {
// scr: code that is not applicable
...
}

return subtreeCount;
}

traverseAllChildrenImpl@shared/utils/traverseAllChildren.js

We have described this method in {section IV},

when it is called the first time (and the type of children parameter is array), it calls itself for every ReactElement within the array; when it is called successively (children is ReactElement), invokes the aforementioned callback that…

“set individual ReactElement with its associated key (name) in an object” as mentioned soon before.

The keys are generated with getComponentKey(),

1
2
3
4
5
6
7
8
9
10
function getComponentKey(component, index) {
if (component && typeof component === 'object' && component.key != null) {
// Explicit key
return KeyEscapeUtils.escape(component.key);
}
// Implicit key determined by the index in the set
return index.toString(36);
}

getComponentKey@shared/utils/traverseAllChildren.js

which basically uses the index of the array as the key in the object (index.toString(36)), in the case that the key is not explicitly set in “key-less nodes”.

The static (sub) call stack of flattenChildren(),

1
2
3
4
5
6
...
flattenChildren()
|-traverseAllChildren()
|-traverseAllChildrenImpl()
|↻traverseAllChildrenImpl() // for direct each child
|-flattenSingleChildIntoContext()

now we have an key-value object nextChildren to be “diffed” with prevChildren.

ReactChildReconciler.updateChildren() — manipulate the virtual DOM tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
updateChildren: function(
prevChildren,
nextChildren,
mountImages,
removedNodes,
transaction,
hostParent,
hostContainerInfo,
context,
selfDebugID, // 0 in production and for roots
) {
if (!nextChildren && !prevChildren) {
return;
}

var name;
var prevChild;

for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}

prevChild = prevChildren && prevChildren[name];
var prevElement = prevChild && prevChild._currentElement;
var nextElement = nextChildren[name];
if ( // scr: -----------------------------------------------> 1)
prevChild != null &&
shouldUpdateReactComponent(prevElement, nextElement)
) {
ReactReconciler.receiveComponent(
prevChild,
nextElement,
transaction,
context,
);
nextChildren[name] = prevChild; // scr: --------------> end 1)
} else {
if (prevChild) { // scr: ---------------------------------> 2)
removedNodes[name] = ReactReconciler.getHostNode(prevChild);
ReactReconciler.unmountComponent(prevChild, false);
}

// The child must be instantiated before it's mounted.
var nextChildInstance = instantiateReactComponent(nextElement, true);
nextChildren[name] = nextChildInstance;
// Creating mount image now ensures refs are resolved in right order
// (see https://github.com/facebook/react/pull/7101 for explanation).
var nextChildMountImage = ReactReconciler.mountComponent(
nextChildInstance,
transaction,
hostParent,
hostContainerInfo,
context,
selfDebugID,
);

mountImages.push(nextChildMountImage);
} // scr: ----------------------------------------------> end 2)
}

// scr: ------------------------------------------------------> 3)
// Unmount children that are no longer present.
for (name in prevChildren) {
if (
prevChildren.hasOwnProperty(name) &&
!(nextChildren && nextChildren.hasOwnProperty(name))
) {
prevChild = prevChildren[name];
removedNodes[name] = ReactReconciler.getHostNode(prevChild);
ReactReconciler.unmountComponent(prevChild, false);
}
} // scr: ------------------------------------------------> end 3)
},

updating is nothing more than modifying, adding, and deleting

This method traverse the nextChildren, and

1) recurse back to ReactReconciler.receiveComponent() to modify the content of the associated DOM nodes as in {last post} if the types of the corresponding “pre” and “next” nodes are the same (judged by shouldUpdateReactComponent() {last post}), the logic branch of which applies to

Figure 39.

and

Figure 40.

as the comparison is based on the counterparts’ index (that is also key);

2) re-mount the virtual DOM if types of “pre” and “next” nodes are different, or the corresponding “pre” node simply does not exist;

As in {section IV}, the virtual DOM’s corresponding li node has been created in the mounting process;

Figure 41.

3) un-mount “pre” virtual DOM(s) if they do not exist in the “next” ones.

The content updating operations are encapsulated in the recursion of ReactReconciler.receiveComponent() {last post}, whilst the operations on real DOM tree are conducted when the logic processes back in ReactMultiChild.updateChildren().

ReactMultiChild.updateChildren() II — matipulate real DOMs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
...
var updates = null;
var name;
// `nextIndex` will increment for each child in `nextChildren`, but
// `lastIndex` will be the last index visited in `prevChildren`.
var nextIndex = 0;
var lastIndex = 0;

// `nextMountIndex` will increment for each newly mounted child.
var nextMountIndex = 0;
var lastPlacedNode = null;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}

// scr: --------------------------------------------------> III)
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
updates = enqueue(
updates,
this.moveChild(
prevChild,
lastPlacedNode,
nextIndex,
lastIndex
)
);

lastIndex = Math.max(prevChild._mountIndex, lastIndex);
prevChild._mountIndex = nextIndex; // scr: ---------> end III)
} else { // scr: ------------------------------------------> IV)
if (prevChild) {
// Update `lastIndex` before `_mountIndex` gets unset by unmounting.
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
// The `removedNodes` loop below will actually remove the child.
}

// The child must be instantiated before it's mounted.
updates = enqueue(
updates,
this._mountChildAtIndex(
nextChild,
mountImages[nextMountIndex],
lastPlacedNode,
nextIndex,
transaction,
context
)
);

nextMountIndex++;
} // scr: ---------------------------------------------> end IV)

nextIndex++;
lastPlacedNode = ReactReconciler.getHostNode(nextChild);
}

// Remove children that are no longer present.
for (name in removedNodes) { // scr: -------------------------> V)
if (removedNodes.hasOwnProperty(name)) {
updates = enqueue(
updates,
this._unmountChild(
prevChildren[name],
removedNodes[name]
)
);
}
} // scr: ------------------------------------------------> end V)

if (updates) {
processQueue(this, updates); // scr: ----------------------> VI)
}

this._renderedChildren = nextChildren;

// scr: DEV code
...

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

This logic block iterates the nextChildren, and when necessary, it

III) mark that a node’s position has changed;

IV) mark a newly added node;

V) mark a removed node;

VI) commit the changes to the DOM tree {last post}

The branch applies here is IV) that adds the ReactElement[4] associated node to the DOM tree.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
_mountChildAtIndex: function (
child,
mountImage,
afterNode,
index,
transaction,
context
) {
child._mountIndex = index;
return this.createChild(child, afterNode, mountImage);
},

createChild: function (child, afterNode, mountImage) {
return makeInsertMarkup(mountImage, afterNode, child._mountIndex);
},

function makeInsertMarkup(markup, afterNode, toIndex) {
// NOTE: Null values reduce hidden classes.
return {
type: 'INSERT_MARKUP',
content: markup,
fromIndex: null,
fromNode: null,
toIndex: toIndex,
afterNode: afterNode
};
}

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

And in VI)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
processUpdates: function(parentNode, updates) {
// scr: DEV code
...

for (var k = 0; k < updates.length; k++) {
var update = updates[k];
switch (update.type) {
case 'INSERT_MARKUP':
insertLazyTreeChildAt(
parentNode,
update.content,
getNodeAfter(parentNode, update.afterNode),
);
break;
// scr: code that is not applicable
...

function insertLazyTreeChildAt(
parentNode,
childTree,
referenceNode
) {
DOMLazyTree.insertTreeBefore(
parentNode,
childTree,
referenceNode
);
}

DOMChildrenOperations@renderers/dom/client/utils/DOMChildrenOperations.js

So the last card in this stack is DOMLazyTree.insertTreeBefore(). We already know from {section II} that this method calls the HTML DOM API

1
parentNode.insertBefore(tree.node, referenceNode);

So what happens when

Diffing nodes with keys

Example 2.

1
2
3
4
5
6
7
8
9
10
11
12
13
...
render() {
return (
<ul>
{
this.state.data.map(function(val, i) {
return <li key={val}>{ val }</li>;
})
}
</ul>
);
}
...

The process logic are the same as in key-less nodes before ReactDOMComponent.flattenChildren(), in which the designated keys instead of the array index will be used to establish the key-value object,

1
2
3
4
5
6
7
8
9
10
11
function getComponentKey(component, index) {
if (component && typeof component === 'object' &&
component.key != null) {
// Explicit key
return KeyEscapeUtils.escape(component.key);
}
// code that is not applicable
...
}

getComponentKey@shared/utils/traverseAllChildren.js

So in ReactChildReconciler.updateChildren() the comparison of the two virtual DOM trees can be better aligned,

Figure 42.

and the recursive ReactReconciler.receiveComponent() does not incur any DOM operations by comparing nodes (key: one and two) with same content , and only the necessary DOM operation, i.e.,

1
parentNode.insertBefore(tree.node, referenceNode);

is conducted for the node (key: new) in ReactMultiChild.updateChildren().

As a result, keys can spare some unnecessary DOM operations for mutating a DOM tree .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class App extends Component {
constructor(props) {
super(props);
this.state = {
mutate: false,
};

this.timer = setInterval(
() => this.tick(),
5000
);
}

tick() {
this.setState({
mutate: true,
});
}

render() {
return (
<ul>
{ this.state.mutate &&
<li>New</li>
}
<li>One</li>
<li>Two</li>
</ul>
);
}
}

export default App;

The above code also changes the DOM tree structure. Can you answer why the keys are not required here?

—End note—

Reading source code with a purpose is like searching an array, in which, theoretically, it is O(n) - O(log n) faster when the array has already been sorted. This series aims to sort out the React code base for you, so you will be able to enjoy the O(log n) whenever having a purpose(s).