Understanding The React Source Code — Initial Rendering (Class Component) IV
Understanding The React Source Code I
Understanding The React Source Code II
Understanding The React Source Code III
Understanding The React Source Code IV (this one)
Understanding The React Source Code V
Understanding The React Source Code VI
Understanding The React Source Code VII
Understanding The React Source Code VIII
Understanding The React Source Code IX
We have completed the rendering process of a simple component. This time we are going to explore more ramifications of this process by discussing how a class component (a typical one we might use in everyday development) is rendered.
Files used in this article: the same as post one and two
I use {} to reference the previous post 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 post one, in which we considered it too complex for beginners. But since we have leveled-up a bit, it does not look that daunting anymore.
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:
ReactDOM.render(
<App />,
document.getElementById(‘root’)
);
Now the babeled code:
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 post.
This time we can fast forward the logic that is shared with simple component.
Construct the top level wrapper `ReactCompositeComponent[T]`
The designated data structure:
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
);
2) creates ReactElement[2]
in _renderSubtreeIntoContainer()
;
3) creates the designated wrapper with instantiateReactComponent()
.
ReactElement.createElement(type, // scr: -------------> App
config, // scr: -------------> null
children // scr: -------------> null
) // scr: ------------------------------------------------------> 1)
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 {post one}.
Initialize `ReactCompositeComponent[T]`
The designated data structure:
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.
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 {post two}.
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 posts for simple component. After those operations complete, the logic processes to the first ramification that is specific to class component.
`ReactCompositeComponent[T].performInitialMount()` — create a `ReactCompositeComponent` from `ReactElement[1]`
This step strips the wrapper and creates another ReactCompositeComponent
instance to reflect the class component, App
.
The designated data structure:
The call stack in action:
...
|~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 {post two}. 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.
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
`ReactCompositeComponent[ins].mountComponent()` — initialize `ReactCompositeComponent[ins]`
The designated data structure:
The call stack in action:
...
|~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()
{post two}, 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:
...
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
);
...ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
in which the constructor of App
gets called.
...
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 yourthis.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]
{post two}, we do not further examine the workhorse method (i.e., mountComponent()
).
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
`ReactCompositeComponent[ins].performInitialMount()` — create a `ReactDOMComponent`
...
|~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:
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 ReactElement
s defined within App[ins]
needs to be extracted. To do so, App[ins].render()
is called by the following line (in _renderValidatedComponent()
) {post two}
...
renderedElement = this._renderValidatedComponent();
...
Then App[ins].render()
triggers
1) The cascading calls of React.createElement()
To understand how the ReactElement
tree is established, let’s first revisit the App.render()
implementation:
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
{post one}.
React.createElement( // scr: --------------------------------> 1)
‘img’,
{ src: "main.jpg", className: ‘App-logo’, alt: ‘logo’ }
),
creates ReactElement[2]
:
; and
React.createElement( // scr: --------------------------------> 2)
‘h1’,
null,
‘Welcome to React’
)
creates ReactElement[3]
:
(Now the two arguments for 3) are resolved.)
; and
React.createElement( // scr: -----------> 3)
'div',
ReactElement[2],
ReactElement[3]
),
creates ReactElement[4]
:
; and
React.createElement( // scr: -----------> 4)
'p',
{ className: 'App-intro' },
this.state.desc
)
creates ReactElement[5]
:
(Now the arguments for 5) are resolved.)
; and
return React.createElement( // scr: -----------> 5)
'div',
{ className: 'App' },
ReactElement[4],
ReactElement[5]
)
creates ReactElement[6]
:
.
Combined together we got the element tree referenced by renderedElement
:
2)`
ReactCompositeComponent[ins]._instantiateReactComponent()` — Create `ReactDOMComponent[6]`
The designated data structure:
The element tree created in the last step is used to create the ReactDOMComponent[6]
by the following line (within _instantiateReactComponent()
) {post two}
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
3) Now ReactReconciler.mountComponent()
calls the mountComponent()
of the ReactDOMComponent[6]
and the logic processes to the lower half.
to be continued…
I hope you like this read. If so, please clap for it or follow me on Medium. Thanks, and I hope to see you the next time.👋
Originally published at holmeshe.me.