mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
175 lines
5.0 KiB
JavaScript
175 lines
5.0 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {DOMContainer} from './ReactDOM';
|
|
import type {RootTag} from 'shared/ReactRootTags';
|
|
import type {ReactNodeList} from 'shared/ReactTypes';
|
|
// TODO: This type is shared between the reconciler and ReactDOM, but will
|
|
// eventually be lifted out to the renderer.
|
|
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
|
|
|
|
export type RootType = {
|
|
render(children: ReactNodeList): void,
|
|
unmount(): void,
|
|
_internalRoot: FiberRoot,
|
|
...
|
|
};
|
|
|
|
export type RootOptions = {
|
|
hydrate?: boolean,
|
|
hydrationOptions?: {
|
|
onHydrated?: (suspenseNode: Comment) => void,
|
|
onDeleted?: (suspenseNode: Comment) => void,
|
|
...
|
|
},
|
|
...
|
|
};
|
|
|
|
import {
|
|
isContainerMarkedAsRoot,
|
|
markContainerAsRoot,
|
|
unmarkContainerAsRoot,
|
|
} from './ReactDOMComponentTree';
|
|
import {eagerlyTrapReplayableEvents} from '../events/ReactDOMEventReplaying';
|
|
import {
|
|
ELEMENT_NODE,
|
|
COMMENT_NODE,
|
|
DOCUMENT_NODE,
|
|
DOCUMENT_FRAGMENT_NODE,
|
|
} from '../shared/HTMLNodeType';
|
|
|
|
import {createContainer, updateContainer} from 'react-reconciler/inline.dom';
|
|
import invariant from 'shared/invariant';
|
|
import {BlockingRoot, ConcurrentRoot, LegacyRoot} from 'shared/ReactRootTags';
|
|
|
|
function ReactDOMRoot(container: DOMContainer, options: void | RootOptions) {
|
|
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
|
|
}
|
|
|
|
function ReactDOMBlockingRoot(
|
|
container: DOMContainer,
|
|
tag: RootTag,
|
|
options: void | RootOptions,
|
|
) {
|
|
this._internalRoot = createRootImpl(container, tag, options);
|
|
}
|
|
|
|
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
|
|
children: ReactNodeList,
|
|
): void {
|
|
if (__DEV__) {
|
|
if (typeof arguments[1] === 'function') {
|
|
console.error(
|
|
'render(...): does not support the second callback argument. ' +
|
|
'To execute a side effect after rendering, declare it in a component body with useEffect().',
|
|
);
|
|
}
|
|
}
|
|
const root = this._internalRoot;
|
|
updateContainer(children, root, null, null);
|
|
};
|
|
|
|
ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void {
|
|
if (__DEV__) {
|
|
if (typeof arguments[0] === 'function') {
|
|
console.error(
|
|
'unmount(...): does not support a callback argument. ' +
|
|
'To execute a side effect after rendering, declare it in a component body with useEffect().',
|
|
);
|
|
}
|
|
}
|
|
const root = this._internalRoot;
|
|
const container = root.containerInfo;
|
|
updateContainer(null, root, null, () => {
|
|
unmarkContainerAsRoot(container);
|
|
});
|
|
};
|
|
|
|
function createRootImpl(
|
|
container: DOMContainer,
|
|
tag: RootTag,
|
|
options: void | RootOptions,
|
|
) {
|
|
// Tag is either LegacyRoot or Concurrent Root
|
|
const hydrate = options != null && options.hydrate === true;
|
|
const hydrationCallbacks =
|
|
(options != null && options.hydrationOptions) || null;
|
|
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
|
|
markContainerAsRoot(root.current, container);
|
|
if (hydrate && tag !== LegacyRoot) {
|
|
const doc =
|
|
container.nodeType === DOCUMENT_NODE
|
|
? container
|
|
: container.ownerDocument;
|
|
eagerlyTrapReplayableEvents(doc);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
export function createRoot(
|
|
container: DOMContainer,
|
|
options?: RootOptions,
|
|
): RootType {
|
|
invariant(
|
|
isValidContainer(container),
|
|
'createRoot(...): Target container is not a DOM element.',
|
|
);
|
|
warnIfReactDOMContainerInDEV(container);
|
|
return new ReactDOMRoot(container, options);
|
|
}
|
|
|
|
export function createBlockingRoot(
|
|
container: DOMContainer,
|
|
options?: RootOptions,
|
|
): RootType {
|
|
invariant(
|
|
isValidContainer(container),
|
|
'createRoot(...): Target container is not a DOM element.',
|
|
);
|
|
warnIfReactDOMContainerInDEV(container);
|
|
return new ReactDOMBlockingRoot(container, BlockingRoot, options);
|
|
}
|
|
|
|
export function createLegacyRoot(
|
|
container: DOMContainer,
|
|
options?: RootOptions,
|
|
): RootType {
|
|
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
|
|
}
|
|
|
|
export function isValidContainer(node: mixed): boolean {
|
|
return !!(
|
|
node &&
|
|
(node.nodeType === ELEMENT_NODE ||
|
|
node.nodeType === DOCUMENT_NODE ||
|
|
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
|
|
(node.nodeType === COMMENT_NODE &&
|
|
(node: any).nodeValue === ' react-mount-point-unstable '))
|
|
);
|
|
}
|
|
|
|
function warnIfReactDOMContainerInDEV(container) {
|
|
if (__DEV__) {
|
|
if (isContainerMarkedAsRoot(container)) {
|
|
if (container._reactRootContainer) {
|
|
console.error(
|
|
'You are calling ReactDOM.createRoot() on a container that was previously ' +
|
|
'passed to ReactDOM.render(). This is not supported.',
|
|
);
|
|
} else {
|
|
console.error(
|
|
'You are calling ReactDOM.createRoot() on a container that ' +
|
|
'has already been passed to createRoot() before. Instead, call ' +
|
|
'root.render() on the existing root instead if you want to update it.',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|