Understanding The React Source Code - Initial Rendering (Simple Component) III

Last time 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 article:

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

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

It calls HTML DOM APIs and hits the bottom.

The designated data structure:

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 post — mountComponentIntoNode().

mountImageIntoNode() — mount the DOM into the container node

First lemme copy back the stack frame from the 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
|=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’)
);

A conclusion so far

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

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

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

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

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

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

`mountImageIntoNode()` — mount the DOM into the container node

—end notes—

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.

React 16 uses fiber reconciler as the new architecture. Go ahead if you can not wait to check it out. Happy hacking!

That's it. Did I make a serious mistake? or miss out on anything important? Or you simply like the read. Link me on -- I'd be chuffed to hear your feedback.