What happens when we call ReactDOM.render
for the first time in a React project?
Today, let’s trace this question from the perspective of the source code (focusing on the overall process rather than getting bogged down in details).
ReactDOMStackEntry
First, we can find the render
method in ReactDOM’s entry file, ReactDOMStackEntry.js
. As you can see, the render
method is provided by the ReactMount
component.
var ReactDOMStack = {
findDOMNode: findDOMNode,
render: ReactMount.render,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
version: ReactVersion,
/* eslint-disable camelcase */
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer,
/* eslint-enable camelcase */
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// For TapEventPlugin which is popular in open source
EventPluginHub: require("EventPluginHub"),
// Used by test-utils
EventPluginRegistry: require("EventPluginRegistry"),
EventPropagators: require("EventPropagators"),
ReactControlledComponent: require("ReactControlledComponent"),
ReactDOMComponentTree,
ReactDOMEventListener: require("ReactDOMEventListener"),
ReactUpdates: ReactUpdates,
},
};
ReactMount.render
render: function(nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(
null,
nextElement,
container,
callback,
);
},
It then calls the _renderSubtreeIntoContainer
method. The core content of this method is as follows:
ReactMount._renderSubtreeIntoContainer
_renderSubtreeIntoContainer: function(
parentComponent,
nextElement,
container,
callback,
) {
callback = callback === undefined ? null : callback;
if (!React.isValidElement(nextElement)) {
...
}
// 创建下一个 wrapped 元素
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
var nextContext = getContextForSubtree(parentComponent);
// 拿到当前的顶层容器组件
var prevComponent = getTopLevelWrapperInContainer(container);
// 对于第一次 render 来说,prevComponent 为 null
if (prevComponent) {
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props.child;
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback =
callback &&
function() {
validateCallback(callback);
callback.call(publicInst);
};
ReactMount._updateRootComponent(
prevComponent,
nextWrappedElement,
nextContext,
container,
updatedCallback,
);
return publicInst;
} else {
ReactMount.unmountComponentAtNode(container);
}
}
var reactRootElement = getReactRootElementInContainer(container);
var containerHasReactMarkup =
reactRootElement && !!internalGetID(reactRootElement);
var containerHasNonRootReactChild = hasNonRootReactChild(container);
var shouldReuseMarkup =
containerHasReactMarkup &&
!prevComponent &&
!containerHasNonRootReactChild;
var component = ReactMount._renderNewRootComponent(
nextWrappedElement,
container,
shouldReuseMarkup,
nextContext,
callback,
)._renderedComponent.getPublicInstance();
return component;
},
Here are a few methods:
getTopLevelWrapperInContainer
shouldUpdateReactComponent
_renderNewRootComponent
getTopLevelWrapperInContainer
This method is used to retrieve the existing top-level container component. The relevant code is as follows.
function getTopLevelWrapperInContainer(container) {
var root = getHostRootInstanceInContainer(container);
return root ? root._hostContainerInfo._topLevelWrapper : null;
}
function getHostRootInstanceInContainer(container) {
var rootEl = getReactRootElementInContainer(container);
var prevHostInstance =
rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl);
return prevHostInstance && !prevHostInstance._hostParent
? prevHostInstance
: null;
}
It calls two methods: getReactRootElementInContainer
and ReactDOMComponentTree.getInstanceFromNode
.
getReactRootElementInContainer
function getReactRootElementInContainer(container) {
if (!container) {
return null;
}
if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}
ReactDOMComponentTree
This module has three methods:
precacheChildNodes
stores the corresponding React instance on the DOM node.getNodeFromInstance
retrieves the corresponding DOM node from an instance.getInstanceFromNode
retrieves the corresponding instance from a DOM node.
shouldUpdateReactComponent
Determines whether the component needs to be updated.
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
);
}
}
The logic is as follows:
- Returns
true
if both the previous and current elements arenull
. - If it’s a
textComponent
, it updates directly. - Otherwise, if it’s a DOM element or a React element, and both
type
andkey
are the same, it returnstrue
and performs an update.
ReactMount._renderNewRootComponent
This method is the core of _renderSubtreeIntoContainer
, used to mount a new component into the DOM.
_renderNewRootComponent: function(
nextElement,
container,
shouldReuseMarkup,
context,
callback,
) {
// 通过 instantiateReactComponent 拿到 React Component 组件实例
var componentInstance = instantiateReactComponent(nextElement, false);
if (callback) {
componentInstance._pendingCallbacks = [
function() {
validateCallback(callback);
callback.call(
componentInstance._renderedComponent.getPublicInstance(),
);
},
];
}
// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context,
);
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
return componentInstance;
},
instantiateReactComponent
Generates different React Components based on the passed parameters. Core code:
if (node === null || node === false) {
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
var element = node;
var type = element.type;
if (typeof type !== 'function' && typeof type !== 'string') {
...
}
// Special case string values
if (typeof element.type === 'string') {
instance = ReactHostComponent.createInternalComponent(element);
} else if (isInternalComponentType(element.type)) {
// This is temporarily available for custom components that are not string
// representations. I.e. ART. Once those are updated to use the string
// representation, we can drop this code path.
instance = new element.type(element);
// We renamed this. Allow the old name for compat. :(
if (!instance.getHostNode) {
instance.getHostNode = instance.getNativeNode;
}
} else {
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
instance = ReactHostComponent.createInstanceForText(node);
} else {
invariant(false, 'Encountered invalid React node of type %s', typeof node);
}
From the code, you can see that based on element.type
, there are three methods to generate three different types of React component instances:
ReactHostComponent.createInternalComponent(element)
new ReactCompositeComponentWrapper(element)
ReactHostComponent.createInstanceForText(node)
Readers might notice that I’ve omitted the code instance = new element.type(element);
which executes when isInternalComponentType(element.type)
is true. This is because it’s a solution for React’s internal encapsulated components not expressed as strings, and we don’t need to concern ourselves with it.
Let’s look at the three methods above; two of them call the ReactHostComponent
module.
ReactHostComponent
Core code:
var ReactHostComponentInjection = {
// This accepts a class that receives the tag string. This is a catch all
// that can render any kind of tag.
injectGenericComponentClass: function (componentClass) {
genericComponentClass = componentClass;
},
// This accepts a text component class that takes the text string to be
// rendered as props.
injectTextComponentClass: function (componentClass) {
textComponentClass = componentClass;
},
};
function createInternalComponent(element) {
invariant(
genericComponentClass,
"There is no registered component for the tag %s",
element.type,
);
return new genericComponentClass(element);
}
/**
* @param {ReactText} text
* @return {ReactComponent}
*/
function createInstanceForText(text) {
return new textComponentClass(text);
}
It provides two methods to create components, and the implementations of these two component classes are injected from other modules. So, where exactly are they injected from?
After some searching, it turns out they are injected in ReactDOMStackInjection.js
. Let’s take a look at the code:
var ReactComponentEnvironment = require("ReactComponentEnvironment");
var ReactComponentBrowserEnvironment = require("ReactComponentBrowserEnvironment");
var ReactDOMComponent = require("ReactDOMComponent");
var ReactDOMComponentTree = require("ReactDOMComponentTree");
var ReactDOMEmptyComponent = require("ReactDOMEmptyComponent");
var ReactDOMTextComponent = require("ReactDOMTextComponent");
var ReactDefaultBatchingStrategy = require("ReactDefaultBatchingStrategy");
var ReactEmptyComponent = require("ReactEmptyComponent");
var ReactGenericBatching = require("ReactGenericBatching");
var ReactHostComponent = require("ReactHostComponent");
var ReactReconcileTransaction = require("ReactReconcileTransaction");
var ReactUpdates = require("ReactUpdates");
var findDOMNode = require("findDOMNode");
var getHostComponentFromComposite = require("getHostComponentFromComposite");
ReactGenericBatching.injection.injectStackBatchedUpdates(
ReactUpdates.batchedUpdates,
);
ReactHostComponent.injection.injectGenericComponentClass(ReactDOMComponent);
ReactHostComponent.injection.injectTextComponentClass(ReactDOMTextComponent);
ReactEmptyComponent.injection.injectEmptyComponentFactory(
function (instantiate) {
return new ReactDOMEmptyComponent(instantiate);
},
);
ReactUpdates.injection.injectReconcileTransaction(ReactReconcileTransaction);
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
ReactComponentEnvironment.injection.injectEnvironment(
ReactComponentBrowserEnvironment,
);
findDOMNode._injectStack(function (inst) {
inst = getHostComponentFromComposite(inst);
return inst ? ReactDOMComponentTree.getNodeFromInstance(inst) : null;
});
As you can see, this module injects ReactDOMComponent
and ReactDOMTextComponent
via ReactHostComponent.injection
. It also injects some other modules, which we will use later.
Here, ReactDOMComponent
and ReactDOMTextComponent
are the modules that truly generate DOM markup. Their content is extensive but the code is relatively simple, so we won’t go into detail here. In short, calling the mountComponent
method of both these modules will generate DOM Markup. The difference is that ReactDOMComponent
will produce markup with the following structure:
{
node: node,
children: [],
html: null,
text: null,
toString
}
Whereas ReactDOMTextComponent
will directly generate String
type text to be rendered in the DOM.
ReactCompositeComponent
The last type of component should be ReactCompositeComponentWrapper
, but looking at these two snippets within instantiateReactComponent
:
var ReactCompositeComponentWrapper = function (element) {
this.construct(element);
};
Object.assign(
ReactCompositeComponentWrapper.prototype,
ReactCompositeComponent,
{
_instantiateReactComponent: instantiateReactComponent,
},
);
When this.construct
is called, it still invokes ReactCompositeComponent
, which represents user-defined components.
ReactUpdates.batchedUpdates
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected();
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
As you can see, ReactUpdates
calls batchedUpdate
via batchingStrategy
. And batchingStrategy
is also injected by ReactDOMStackInjection
, as mentioned earlier.
ReactUpdates.injection.injectReconcileTransaction(ReactReconcileTransaction);
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
ReactUpdates
injects two modules: ReactReconcileTransaction
and ReactDefaultBatchingStrategy
.
Let’s first look at ReactDefaultBatchingStrategy
; we’ll discuss ReactReconcileTransaction
when we encounter it later. Here’s its code:
var ReactUpdates = require("ReactUpdates");
var Transaction = require("Transaction");
var emptyFunction = require("fbjs/lib/emptyFunction");
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
},
});
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
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) {
// 如果当前 updates 已经完成,那么直接调用 callback
return callback(a, b, c, d, e);
} else {
// 通过事务的方式去调用 callback
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
As you can see, the ReactUpdates.batchUpdates
we called earlier actually invokes batchedUpdates
here, and its logic is quite simple.
A transaction
method is used here, which I understand as a “transaction.” This means that if an update is currently in progress, the callback is invoked in a transactional manner.
transaction
Transactions are widely used in React’s source code to invoke a method in a transactional way.
It wraps a method with one or more wrappers, which are executed sequentially before and after the method call. The transaction ensures that both the initialize
and close
methods of the wrapper are executed, regardless of whether the method being executed succeeds, fails, or throws an error. Its source code explanation makes this clear:
* <pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
Now, let’s return to ReactDefaultBatchingStrategy
. There are two wrappers in the code:
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
Their initialize
methods are both empty functions, and their close
methods are:
- Sets the current update status to
false
. flushBatchedUpdates
is a more complex method that won’t be elaborated on here. Its main purpose is to ensure all components are updated correctly (flushBatchedUpdates->ReactUpdates.runBatchedUpdates->ReactCompositeComponent.performUpdateIfNecessary
).
Now, back to the code above:
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context,
);
We can see that batchedMountComponentIntoNode
is called here to perform subsequent work.
function batchedMountComponentIntoNode(
componentInstance,
container,
shouldReuseMarkup,
context,
) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
/* useCreateElement */
!shouldReuseMarkup,
);
transaction.perform(
mountComponentIntoNode,
null,
componentInstance,
container,
transaction,
shouldReuseMarkup,
context,
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
The first assignment statement here uses ReactReconcileTransaction
, another module injected into ReactUpdates
, which is React’s reconciliation transaction module.
var SELECTION_RESTORATION = {
initialize: ReactInputSelection.getSelectionInformation,
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 = {
initialize: function() {
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
return currentlyEnabled;
},
close: function(previouslyEnabled) {
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
},
};
/**
* Provides a queue for collecting `componentDidMount` and
* `componentDidUpdate` callbacks during the transaction.
*/
var ON_DOM_READY_QUEUEING = {
initialize: function() {
this.reactMountReady.reset();
},
close: function() {
this.reactMountReady.notifyAll();
},
};
...
var TRANSACTION_WRAPPERS = [
SELECTION_RESTORATION,
EVENT_SUPPRESSION,
ON_DOM_READY_QUEUEING,
];
function ReactReconcileTransaction(useCreateElement) {
this.reinitializeTransaction();
this.renderToStaticMarkup = false;
this.reactMountReady = CallbackQueue.getPooled();
this.useCreateElement = useCreateElement;
}
...
var Mixin = {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
...
}
Object.assign(ReactReconcileTransaction.prototype, Transaction, Mixin);
PooledClass.addPoolingTo(ReactReconcileTransaction);
This is also called in a transactional manner, and it has three wrappers:
- Selection Restoration: Minimizes disruption to the user’s selection range during updates.
- Event Suppression: Suppresses unnecessary event dispatches, such as
blur
events caused by temporarily removing aninput
element. - On DOM Ready Queueing: Provides a queue for
componentDidMount
andcomponentDidUpdate
callback functions during transaction execution.
The subsequent getPooled
method is a technique that utilizes an instance pool to avoid unnecessary garbage collection (GC), which won’t be explained further here.
Next, mountComponentIntoNode
is called using this transactional approach. Let’s examine this method in detail.
mountComponentIntoNode
function mountComponentIntoNode(
wrapperInstance,
container,
transaction,
shouldReuseMarkup,
context,
) {
var markup = ReactReconciler.mountComponent(
wrapperInstance,
transaction,
null,
ReactDOMContainerInfo(wrapperInstance, container),
context,
0 /* parentDebugID */,
);
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
ReactMount._mountImageIntoNode(
markup,
container,
wrapperInstance,
shouldReuseMarkup,
transaction,
);
}
Here, we see the operation of the key variable markup, which is the object we ultimately render into the DOM. The markup
is obtained via the ReactReconciler.mountComponent
method. The source code for ReactReconciler.mountComponent
is as follows:
mountComponent: function(
internalInstance,
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID, // 0 in production and for roots
) {
var markup = internalInstance.mountComponent(
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID,
);
if (
internalInstance._currentElement &&
internalInstance._currentElement.ref != null
) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
return markup;
},
It then calls internalInstance.mountComponent
. Here, internalInstance
is essentially the React Component instance obtained earlier via instantiateReactComponent
.
// _renderNewRootComponent
var componentInstance = instantiateReactComponent(nextElement, false);
Here, nextElement
is the React root element to be rendered.
// _renderSubtreeIntoContainer
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
The implementation of TopLevelWrapper
; pay attention to its render
method.
var TopLevelWrapper = function () {
this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
TopLevelWrapper.prototype.render = function () {
return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
The this.props.child
returned within its render
method is nextElement
, which is <App/>
from our project’s entry point ReactDOM.render(<App/>,document.getElementById('root'))
.
Returning to internalInstance.mountComponent
within ReactReconciler.mountComponent
. As discussed earlier with instantiateReactComponent
, we know there are three types of components returned:
ReactDOMComponent
ReactDOMTextComponent
ReactCompositeComponent
The first two types are straightforward; they are native DOM elements and will ultimately render their corresponding Markup. However, ReactCompositeComponent
is more complex, so we’ll only look at the key code:
//ReactCompositeComponent.mountComponent
var Component = this._currentElement.type;
var updateQueue = transaction.getUpdateQueue();
// Initialize the public class
var doConstruct = shouldConstruct(Component);
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
);
var renderedElement;
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
inst = new StatelessComponent(Component);
this._compositeType = ReactCompositeComponentTypes.StatelessFunctional;
} else {
。...
}
markup = this.performInitialMount(
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
);
First, inst
is obtained. The call stack to get inst
is: this._constructComponent -> this._constructComponentWithoutOwner
.
// this._constructComponentWithoutOwner
...
var Component = this._currentElement.type;
if (doConstruct) {
if (__DEV__) {
return measureLifeCyclePerf(
() => new Component(publicProps, publicContext, updateQueue),
this._debugID,
'ctor',
);
} else {
return new Component(publicProps, publicContext, updateQueue);
}
}
...
A Component instance is generated based on _currentElement.type
, where currentElement.type
is either a class inheriting from React.Component
or a pure functional component.
Then renderElement
is declared. For stateless (functional) components, renderElement = inst
; otherwise, it’s undefined
.
Next, let’s look at performInitialMount
.
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID,
);
For non-stateless components, renderedElement
needs to be assigned. The call stack is: this._renderValidatedComponent -> this._renderValidatedComponentWithoutOwnerOrContext
.
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
var renderedElement;
if (__DEV__) {
renderedElement = measureLifeCyclePerf(
() => inst.render(),
this._debugID,
'render',
);
} else {
renderedElement = inst.render();
}
if (__DEV__) {
// We allow auto-mocks to proceed as if they're returning null.
if (renderedElement === undefined && inst.render._isMockFunction) {
// This is probably bad practice. Consider warning here and
// deprecating this convenience.
renderedElement = null;
}
}
return renderedElement;
},
Ultimately, renderedElement
is the result of inst.render()
.
Moving on, child = this._instantiateReactComponent(renderedElement)
and markup = ReactReconciler.mountComponent(child,...)
.
It can be inferred that ReactReconciler.mountComponent
will be called repeatedly and recursively here until child
is no longer a ReactCompositeComponent
, yielding the final Markup. The data structure of Markup can be found in DOMLazyTree
:
// DOMLazyTree
{
node: node,
children: [],
html: null,
text: null,
toString,
}
Once the Markup is obtained, only the final step remains: mounting the Markup into the actual DOM via ReactMount._mountImageIntoNode
.
ReactMount._mountImageIntoNode
_mountImageIntoNode: function(
markup,
container,
instance,
shouldReuseMarkup,
transaction,
) {
invariant(
isValidContainer(container),
'mountComponentIntoNode(...): Target container is not valid.',
);
// 首次渲染,这里为 shouldReuseMarkup = false ,里面都是更新的逻辑
if (shouldReuseMarkup) {
...
}
// 首次渲染,transaction.useCreateElement = true
if (transaction.useCreateElement) {
while (container.lastChild) {
container.removeChild(container.lastChild);
}
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
setInnerHTML(container, markup);
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
}
},
The logic is simple: there are two ways to render markup into the DOM:
- Clears the given container component, then inserts the markup into the given container.
- Calls
setInnerHTML
to insert the markup into the given container and caches the virtual DOM onto the actual DOM node.
For the initial render, the first method is executed: clearing the container component and mounting the markup into the actual DOM. Call stack: DOMLazyTree.insertTreeBefore -> insertTreeChildren
.
function insertTreeChildren(tree) {
if (!enableLazy) {
return;
}
var node = tree.node;
var children = tree.children;
if (children.length) {
for (var i = 0; i < children.length; i++) {
insertTreeBefore(node, children[i], null);
}
} else if (tree.html != null) {
setInnerHTML(node, tree.html);
} else if (tree.text != null) {
setTextContent(node, tree.text);
}
}
Recursively mounts all child components into the DOM.
Summary
At this point, the entire logic for the initial render is complete. Overall, React and ReactDOM’s code is highly abstract, making it quite intricate to read. This article has focused solely on the overall process, without delving into every detail, as there are simply too many.
Finally, I’ve compiled a flowchart illustrating the process after ReactDOM.render
executes, which can help in understanding the entire flow to some extent:
The image is large and may not be clear when clicked and enlarged. It’s recommended to save it locally for a clearer view.
Related Articles
- {% post_link react-source-code-analyze-1 React Source Code Analysis - Entry File %}
- {% post_link react-source-code-analyze-2 React Source Code Analysis - ReactBaseClasses %}
- {% post_link react-source-code-analyze-3 React Source Code Analysis - ReactChildren %}
- {% post_link react-source-code-analyze-4 React Source Code Analysis - ReactElement %}
- {% post_link react-source-code-analyze-5 React Source Code Analysis - onlyChildren %}
This article was published on September 21, 2017 and last updated on September 21, 2017, 2937 days ago. The content may be outdated.