mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
d3e0869324
* Move flushSync warning to React DOM When you call in `flushSync` from an effect, React fires a warning. I've moved the implementation of this warning out of the reconciler and into React DOM. `flushSync` is a renderer API, not an isomorphic API, because it has behavior that was designed specifically for the constraints of React DOM. The equivalent API in a different renderer may not be the same. For example, React Native has a different threading model than the browser, so it might not make sense to expose a `flushSync` API to the JavaScript thread. * Make root.unmount() synchronous When you unmount a root, the internal state that React stores on the DOM node is immediately cleared. So, we should also synchronously delete the React tree. You should be able to create a new root using the same container.
296 lines
8.9 KiB
JavaScript
296 lines
8.9 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 {Container} from './ReactDOMHostConfig';
|
|
import type {MutableSource, ReactNodeList} from 'shared/ReactTypes';
|
|
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
|
|
|
export type RootType = {
|
|
render(children: ReactNodeList): void,
|
|
unmount(): void,
|
|
_internalRoot: FiberRoot | null,
|
|
...
|
|
};
|
|
|
|
export type CreateRootOptions = {
|
|
// TODO: Remove these options.
|
|
hydrate?: boolean,
|
|
hydrationOptions?: {
|
|
onHydrated?: (suspenseNode: Comment) => void,
|
|
onDeleted?: (suspenseNode: Comment) => void,
|
|
mutableSources?: Array<MutableSource<any>>,
|
|
...
|
|
},
|
|
// END OF TODO
|
|
unstable_strictMode?: boolean,
|
|
unstable_concurrentUpdatesByDefault?: boolean,
|
|
...
|
|
};
|
|
|
|
export type HydrateRootOptions = {
|
|
// Hydration options
|
|
hydratedSources?: Array<MutableSource<any>>,
|
|
onHydrated?: (suspenseNode: Comment) => void,
|
|
onDeleted?: (suspenseNode: Comment) => void,
|
|
// Options for all roots
|
|
unstable_strictMode?: boolean,
|
|
unstable_concurrentUpdatesByDefault?: boolean,
|
|
...
|
|
};
|
|
|
|
import {
|
|
isContainerMarkedAsRoot,
|
|
markContainerAsRoot,
|
|
unmarkContainerAsRoot,
|
|
} from './ReactDOMComponentTree';
|
|
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
|
|
import {
|
|
ELEMENT_NODE,
|
|
COMMENT_NODE,
|
|
DOCUMENT_NODE,
|
|
DOCUMENT_FRAGMENT_NODE,
|
|
} from '../shared/HTMLNodeType';
|
|
|
|
import {
|
|
createContainer,
|
|
updateContainer,
|
|
findHostInstanceWithNoPortals,
|
|
registerMutableSourceForHydration,
|
|
flushSync,
|
|
isAlreadyRendering,
|
|
} from 'react-reconciler/src/ReactFiberReconciler';
|
|
import invariant from 'shared/invariant';
|
|
import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
|
|
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
|
|
|
|
function ReactDOMRoot(internalRoot: FiberRoot) {
|
|
this._internalRoot = internalRoot;
|
|
}
|
|
|
|
ReactDOMRoot.prototype.render = function(children: ReactNodeList): void {
|
|
const root = this._internalRoot;
|
|
if (root === null) {
|
|
invariant(false, 'Cannot update an unmounted root.');
|
|
}
|
|
|
|
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 container = root.containerInfo;
|
|
|
|
if (container.nodeType !== COMMENT_NODE) {
|
|
const hostInstance = findHostInstanceWithNoPortals(root.current);
|
|
if (hostInstance) {
|
|
if (hostInstance.parentNode !== container) {
|
|
console.error(
|
|
'render(...): It looks like the React-rendered content of the ' +
|
|
'root container was removed without using React. This is not ' +
|
|
'supported and will cause errors. Instead, call ' +
|
|
"root.unmount() to empty a root's container.",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
updateContainer(children, root, null, null);
|
|
};
|
|
|
|
ReactDOMRoot.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;
|
|
if (root !== null) {
|
|
this._internalRoot = null;
|
|
const container = root.containerInfo;
|
|
if (__DEV__) {
|
|
if (isAlreadyRendering()) {
|
|
console.error(
|
|
'Attempted to synchronously unmount a root while React was already ' +
|
|
'rendering. React cannot finish unmounting the root until the ' +
|
|
'current render has completed, which may lead to a race condition.',
|
|
);
|
|
}
|
|
}
|
|
flushSync(() => {
|
|
updateContainer(null, root, null, null);
|
|
});
|
|
unmarkContainerAsRoot(container);
|
|
}
|
|
};
|
|
|
|
export function createRoot(
|
|
container: Container,
|
|
options?: CreateRootOptions,
|
|
): RootType {
|
|
invariant(
|
|
isValidContainerLegacy(container),
|
|
'createRoot(...): Target container is not a DOM element.',
|
|
);
|
|
warnIfReactDOMContainerInDEV(container);
|
|
|
|
// TODO: Delete these options
|
|
const hydrate = options != null && options.hydrate === true;
|
|
const hydrationCallbacks =
|
|
(options != null && options.hydrationOptions) || null;
|
|
const mutableSources =
|
|
(options != null &&
|
|
options.hydrationOptions != null &&
|
|
options.hydrationOptions.mutableSources) ||
|
|
null;
|
|
// END TODO
|
|
|
|
const isStrictMode = options != null && options.unstable_strictMode === true;
|
|
let concurrentUpdatesByDefaultOverride = null;
|
|
if (allowConcurrentByDefault) {
|
|
concurrentUpdatesByDefaultOverride =
|
|
options != null && options.unstable_concurrentUpdatesByDefault != null
|
|
? options.unstable_concurrentUpdatesByDefault
|
|
: null;
|
|
}
|
|
|
|
const root = createContainer(
|
|
container,
|
|
ConcurrentRoot,
|
|
hydrate,
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
);
|
|
markContainerAsRoot(root.current, container);
|
|
|
|
const rootContainerElement =
|
|
container.nodeType === COMMENT_NODE ? container.parentNode : container;
|
|
listenToAllSupportedEvents(rootContainerElement);
|
|
|
|
// TODO: Delete this path
|
|
if (mutableSources) {
|
|
for (let i = 0; i < mutableSources.length; i++) {
|
|
const mutableSource = mutableSources[i];
|
|
registerMutableSourceForHydration(root, mutableSource);
|
|
}
|
|
}
|
|
// END TODO
|
|
|
|
return new ReactDOMRoot(root);
|
|
}
|
|
|
|
export function hydrateRoot(
|
|
container: Container,
|
|
initialChildren: ReactNodeList,
|
|
options?: HydrateRootOptions,
|
|
): RootType {
|
|
invariant(
|
|
isValidContainer(container),
|
|
'hydrateRoot(...): Target container is not a DOM element.',
|
|
);
|
|
warnIfReactDOMContainerInDEV(container);
|
|
|
|
// For now we reuse the whole bag of options since they contain
|
|
// the hydration callbacks.
|
|
const hydrationCallbacks = options != null ? options : null;
|
|
const mutableSources = (options != null && options.hydratedSources) || null;
|
|
const isStrictMode = options != null && options.unstable_strictMode === true;
|
|
|
|
let concurrentUpdatesByDefaultOverride = null;
|
|
if (allowConcurrentByDefault) {
|
|
concurrentUpdatesByDefaultOverride =
|
|
options != null && options.unstable_concurrentUpdatesByDefault != null
|
|
? options.unstable_concurrentUpdatesByDefault
|
|
: null;
|
|
}
|
|
|
|
const root = createContainer(
|
|
container,
|
|
ConcurrentRoot,
|
|
true, // hydrate
|
|
hydrationCallbacks,
|
|
isStrictMode,
|
|
concurrentUpdatesByDefaultOverride,
|
|
);
|
|
markContainerAsRoot(root.current, container);
|
|
// This can't be a comment node since hydration doesn't work on comment nodes anyway.
|
|
listenToAllSupportedEvents(container);
|
|
|
|
if (mutableSources) {
|
|
for (let i = 0; i < mutableSources.length; i++) {
|
|
const mutableSource = mutableSources[i];
|
|
registerMutableSourceForHydration(root, mutableSource);
|
|
}
|
|
}
|
|
|
|
// Render the initial children
|
|
updateContainer(initialChildren, root, null, null);
|
|
|
|
return new ReactDOMRoot(root);
|
|
}
|
|
|
|
export function isValidContainer(node: any): boolean {
|
|
return !!(
|
|
node &&
|
|
(node.nodeType === ELEMENT_NODE ||
|
|
node.nodeType === DOCUMENT_NODE ||
|
|
node.nodeType === DOCUMENT_FRAGMENT_NODE)
|
|
);
|
|
}
|
|
|
|
// TODO: Remove this function which also includes comment nodes.
|
|
// We only use it in places that are currently more relaxed.
|
|
export function isValidContainerLegacy(node: any): 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 (
|
|
container.nodeType === ELEMENT_NODE &&
|
|
((container: any): Element).tagName &&
|
|
((container: any): Element).tagName.toUpperCase() === 'BODY'
|
|
) {
|
|
console.error(
|
|
'createRoot(): Creating roots directly with document.body is ' +
|
|
'discouraged, since its children are often manipulated by third-party ' +
|
|
'scripts and browser extensions. This may lead to subtle ' +
|
|
'reconciliation issues. Try using a container element created ' +
|
|
'for your app.',
|
|
);
|
|
}
|
|
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.',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|