Photo by Jez Timms on Unsplash
同学,本篇也有中文版哦
In the last article we went through the process that creates a ReactCompositeComponent
instance from JSX expression. This one will continue the walk-through of simple component rendering, from the point of batchedMountComponentIntoNode()
.
Files used in this article:
renderers/dom/client/ReactMount.js: defines mountComponentIntoNode()
, entry point of the logic process in this article, and ReactDOMContainerInfo
which is used in DOM rendering
renderers/shared/stack/reconciler/ReactReconciler.js: call mountComponent
of various ReactXXXComponent
renderers/shared/stack/reconciler/ReactCompositeComponent.js: call mountComponent
to instantiate TopLevelWrapper
, and performInitialMount
to instantiate ReactDOMComponent
renderers/dom/shared/ReactDOMComponent.js: define ReactDOMComponent
A complete static call hierarchy used in this article:
|=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
andbatched updates
in later articles.
`mountComponentIntoNode()` — the cross point of the two halves
mountComponentIntoNode()
is the cross point of the platform agnostic code (in this article referred as “upper half”) and HTML DOM specific one (“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.
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=>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]
.
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()
:
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 of this constructor is ReactDOMContainerInfo[ins]
which 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.
`ReactCompositeComponent.mountComponent()` — 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:
The call stack in action is:
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 show the implementation for small functions (when they are get called) but give return value directly for the sake of concision.
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
, I will its usage very soon;
2) extracts TopLevelWrapper
from this._currentElement.type
, and calls its constructor to create a TopLevelWrapper
instance. In this process:
shouldConstruct(Component)
checks if TopLevelWrapper.prototype.isReactComponent
is set, and returns true
if so; and
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
in the previous post, 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 posts;
I type 4 stars **** here as you might need to come back to check
ReactInstanceMap
‘s origin.
5) goes to the next step.
`ReactCompositeComponent.performInitialMount()` — create a `ReactDOMComponent` from `ReactElement[1]`
This step strips the wrapper and creates a ReactDOMComponent
instance. Plus, we are going to see ReactHostComponent
the first time. This component is used to chain the function call from the upper half to lower half.
First thing first, the target data structure:
The call stack in action:
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? It is “initial” for the nextmountComponent
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 post. 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
...
// 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
:
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 posts.
I type *5 here.
The constructor of ReactDOMComponent
is very similar to that of ReactCompositeComponent
:
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 process goes to lower half.
to be continued…
The post Understanding The React Source Code — Initial Rendering (Simple Component) II appeared first on Crypto Currency Online.
source https://cryptocurrencyonline.co/understanding-the-react-source-code-initial-rendering-simple-component-ii/
No comments:
Post a Comment