ReactDOM
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L1121
const ReactDOM: Object = {
createPortal,
findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
return (componentOrElement: any);
}
const inst = ReactInstanceMap.get(componentOrElement);
if (inst) {
return DOMRenderer.findHostInstance(inst);
}
if (typeof componentOrElement.render === 'function') {
invariant(false, 'Unable to find node on an unmounted component.');
} else {
invariant(
false,
'Element appears to be neither ReactComponent nor DOMNode. Keys: %s',
Object.keys(componentOrElement),
);
}
},
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// For TapEventPlugin which is popular in open source
EventPluginHub,
// Used by test-utils
EventPluginRegistry,
EventPropagators,
ReactControlledComponent,
ReactDOMComponentTree,
ReactDOMEventListener,
},
}
legacyRenderSubtreeIntoContainer
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L1041
If root object which the DOM to render does not have internal property _reactRootContainer
does not exsist, create DOM container from scratch by calling legacyCreateRootFromDOMContainer
. If there is Root object container._reactRootContainer
, it will return instance of ReactRoot
.
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
let root: Root = (container._reactRootContainer: any);
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return DOMRenderer.getPublicRootInstance(root._internalRoot);
}
legacyCreateRootFromDOMContainer
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L996
In case of , this function creates root container for your app.
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
}
// Legacy roots are not async by default.
const isAsync = false;
return new ReactRoot(container, isAsync, shouldHydrate);
}
ReactRoot
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L377
ReactRoot is constructor function to create root container and the instance of ReactRoot will have _internalRoot
. Interesting things are that the methods render,unmount,legacy_renderSubtreeIntoContainer
call DOMRenderer.updateContainer
function and return instance of React.work
.
_internalRoot | Property | contain object returned from DOMRenderer.createContainer | https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L378 |
---|---|---|---|
render | Method | If parent component exsists, this will be called from legacyRenderSubtreeIntoContainer | https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L381 |
unmount | Method | https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L397 | |
legacy_renderSubtreeIntoContainer | Method | If parent component does not exsist, this will be called from legacyRenderSubtreeIntoContainer | https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L410 |
createBatch | Method | https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L429 |
function ReactRoot(container: Container, isAsync: boolean, hydrate: boolean) {
const root = DOMRenderer.createContainer(container, isAsync, hydrate);
this._internalRoot = root;
}
/**
* render
*/
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
};
/**
* unmount
*/
ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(null, root, null, work._onCommit);
return work;
};
/**
* legacy_renderSubtreeIntoContainer
*/
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, parentComponent, work._onCommit);
return work;
};
/**
* createBatch
*/
ReactRoot.prototype.createBatch = function(): Batch {
const batch = new ReactBatch(this);
const expirationTime = batch._expirationTime;
const internalRoot = this._internalRoot;
const firstBatch = internalRoot.firstBatch;
if (firstBatch === null) {
internalRoot.firstBatch = batch;
batch._next = null;
} else {
// Insert sorted by expiration time then insertion order
let insertAfter = null;
let insertBefore = firstBatch;
while (
insertBefore !== null &&
insertBefore._expirationTime <= expirationTime
) {
insertAfter = insertBefore;
insertBefore = insertBefore._next;
}
batch._next = insertBefore;
if (insertAfter !== null) {
insertAfter._next = batch;
}
}
return batch;
};
DOMRenderer
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L506
ReactFiberReconciler
is another library and return new object. https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberReconciler.js#L412. The object passed to ReactFiberReconciler
is config.
const DOMRenderer = ReactFiberReconciler({
getRootHostContext(rootContainerInstance: Container): HostContext {
let type;
let namespace;
const nodeType = rootContainerInstance.nodeType;
switch (nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
let root = (rootContainerInstance: any).documentElement;
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
break;
}
default: {
const container: any =
nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
const ownNamespace = container.namespaceURI || null;
type = container.tagName;
namespace = getChildNamespace(ownNamespace, type);
break;
}
}
return namespace;
},
getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
const parentNamespace = ((parentHostContext: any): HostContextProd);
return getChildNamespace(parentNamespace, type);
},
getPublicInstance(instance) {
return instance;
},
prepareForCommit(): void {
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
selectionInformation = ReactInputSelection.getSelectionInformation();
ReactBrowserEventEmitter.setEnabled(false);
},
resetAfterCommit(): void {
ReactInputSelection.restoreSelection(selectionInformation);
selectionInformation = null;
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
eventsEnabled = null;
},
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
},
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
},
finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
},
prepareUpdate(
domElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Array<mixed> {
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
},
shouldSetTextContent(type: string, props: Props): boolean {
return (
type === 'textarea' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
typeof props.dangerouslySetInnerHTML.__html === 'string')
);
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
},
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
},
now: ReactDOMFrameScheduling.now,
mutation: {
commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Despite the naming that might imply otherwise, this method only
// fires if there is an `Update` effect scheduled during mounting.
// This happens if `finalizeInitialChildren` returns `true` (which it
// does to implement the `autoFocus` attribute on the client). But
// there are also other cases when this might happen (such as patching
// up text content during hydration mismatch). So we'll check this again.
if (shouldAutoFocusHostComponent(type, newProps)) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
}
},
commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
},
resetTextContent(domElement: Instance): void {
setTextContent(domElement, '');
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
},
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
},
appendChildToContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, container);
} else {
container.appendChild(child);
}
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
},
insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
},
removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.removeChild(child);
},
removeChildFromContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
},
},
hydration: {
canHydrateInstance(
instance: Instance | TextInstance,
type: string,
props: Props,
): null | Instance {
if (
instance.nodeType !== ELEMENT_NODE ||
type.toLowerCase() !== instance.nodeName.toLowerCase()
) {
return null;
}
// This has now been refined to an element node.
return ((instance: any): Instance);
},
canHydrateTextInstance(
instance: Instance | TextInstance,
text: string,
): null | TextInstance {
if (text === '' || instance.nodeType !== TEXT_NODE) {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return null;
}
// This has now been refined to a text node.
return ((instance: any): TextInstance);
},
getNextHydratableSibling(
instance: Instance | TextInstance,
): null | Instance | TextInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
while (
node &&
node.nodeType !== ELEMENT_NODE &&
node.nodeType !== TEXT_NODE
) {
node = node.nextSibling;
}
return (node: any);
},
getFirstHydratableChild(
parentInstance: Container | Instance,
): null | Instance | TextInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
while (
next &&
next.nodeType !== ELEMENT_NODE &&
next.nodeType !== TEXT_NODE
) {
next = next.nextSibling;
}
return (next: any);
},
hydrateInstance(
instance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): null | Array<mixed> {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);
let parentNamespace: string;
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
return diffHydratedProperties(
instance,
type,
props,
parentNamespace,
rootContainerInstance,
);
},
hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);
return diffHydratedText(textInstance, text);
},
didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
) {
if (__DEV__) {
warnForUnmatchedText(textInstance, text);
}
},
didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForUnmatchedText(textInstance, text);
}
},
didNotHydrateContainerInstance(
parentContainer: Container,
instance: Instance | TextInstance,
) {
if (__DEV__) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
}
}
},
didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: Instance | TextInstance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
}
}
},
didNotFindHydratableContainerInstance(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
},
didNotFindHydratableContainerTextInstance(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
},
didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
},
didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedText(parentInstance, text);
}
},
},
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
cancelDeferredCallback: ReactDOMFrameScheduling.cIC,
});
ReactGenericBatching.injection.injectRenderer(DOMRenderer);