/** * 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 {RootTag} from 'react-reconciler/src/ReactRootTags'; 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, ... }; export type RootOptions = { hydrate?: boolean, hydrationOptions?: { onHydrated?: (suspenseNode: Comment) => void, onDeleted?: (suspenseNode: Comment) => void, mutableSources?: Array>, ... }, 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, } from 'react-reconciler/src/ReactFiberReconciler'; import invariant from 'shared/invariant'; import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags'; import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags'; function ReactDOMRoot(container: Container, options: void | RootOptions) { this._internalRoot = createRootImpl(container, ConcurrentRoot, options); } function ReactDOMLegacyRoot(container: Container, options: void | RootOptions) { this._internalRoot = createRootImpl(container, LegacyRoot, options); } ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function( children: ReactNodeList, ): void { const root = this._internalRoot; 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 = ReactDOMLegacyRoot.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: Container, 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 mutableSources = (options != null && options.hydrationOptions != null && options.hydrationOptions.mutableSources) || 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, tag, hydrate, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, ); markContainerAsRoot(root.current, container); const rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container; listenToAllSupportedEvents(rootContainerElement); if (mutableSources) { for (let i = 0; i < mutableSources.length; i++) { const mutableSource = mutableSources[i]; registerMutableSourceForHydration(root, mutableSource); } } return root; } export function createRoot( container: Container, options?: RootOptions, ): RootType { invariant( isValidContainer(container), 'createRoot(...): Target container is not a DOM element.', ); warnIfReactDOMContainerInDEV(container); return new ReactDOMRoot(container, options); } export function createLegacyRoot( container: Container, options?: RootOptions, ): RootType { return new ReactDOMLegacyRoot(container, 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 ( 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.', ); } } } }