diff --git a/packages/react-dom/src/client/DOMPropertyOperations.js b/packages/react-dom/src/client/DOMPropertyOperations.ts similarity index 64% rename from packages/react-dom/src/client/DOMPropertyOperations.js rename to packages/react-dom/src/client/DOMPropertyOperations.ts index 9a186827fa700..8022ebc118d30 100644 --- a/packages/react-dom/src/client/DOMPropertyOperations.js +++ b/packages/react-dom/src/client/DOMPropertyOperations.ts @@ -8,31 +8,31 @@ */ import { - getPropertyInfo, - shouldIgnoreAttribute, - shouldRemoveAttribute, - isAttributeNameSafe, - BOOLEAN, - OVERLOADED_BOOLEAN, -} from '../shared/DOMProperty'; +getPropertyInfo, +shouldIgnoreAttribute, +shouldRemoveAttribute, +isAttributeNameSafe, +BOOLEAN, +OVERLOADED_BOOLEAN } from +'../shared/DOMProperty'; -import type {PropertyInfo} from '../shared/DOMProperty'; +import { PropertyInfo } from '../shared/DOMProperty'; /** - * Get the value for a property on a node. Only used in DEV for SSR validation. - * The "expected" argument is used as a hint of what the expected value is. - * Some properties have multiple equivalent values. - */ + * Get the value for a property on a node. Only used in DEV for SSR validation. + * The "expected" argument is used as a hint of what the expected value is. + * Some properties have multiple equivalent values. + */ export function getValueForProperty( - node: Element, - name: string, - expected: mixed, - propertyInfo: PropertyInfo, -): mixed { +node: Element, +name: string, +expected: unknown, +propertyInfo: PropertyInfo) +: unknown { if (__DEV__) { if (propertyInfo.mustUseProperty) { - const {propertyName} = propertyInfo; - return (node: any)[propertyName]; + const { propertyName } = propertyInfo; + return (node as any)[propertyName]; } else { const attributeName = propertyInfo.attributeName; @@ -47,7 +47,7 @@ export function getValueForProperty( if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { return value; } - if (value === '' + (expected: any)) { + if (value === '' + (expected as any)) { return expected; } return value; @@ -72,7 +72,7 @@ export function getValueForProperty( if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { return stringValue === null ? expected : stringValue; - } else if (stringValue === '' + (expected: any)) { + } else if (stringValue === '' + (expected as any)) { return expected; } else { return stringValue; @@ -82,15 +82,15 @@ export function getValueForProperty( } /** - * Get the value for a attribute on a node. Only used in DEV for SSR validation. - * The third argument is used as a hint of what the expected value is. Some - * attributes have multiple equivalent values. - */ + * Get the value for a attribute on a node. Only used in DEV for SSR validation. + * The third argument is used as a hint of what the expected value is. Some + * attributes have multiple equivalent values. + */ export function getValueForAttribute( - node: Element, - name: string, - expected: mixed, -): mixed { +node: Element, +name: string, +expected: unknown) +: unknown { if (__DEV__) { if (!isAttributeNameSafe(name)) { return; @@ -99,7 +99,7 @@ export function getValueForAttribute( return expected === undefined ? undefined : null; } const value = node.getAttribute(name); - if (value === '' + (expected: any)) { + if (value === '' + (expected as any)) { return expected; } return value; @@ -107,18 +107,18 @@ export function getValueForAttribute( } /** - * Sets the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - * @param {*} value - */ + * Sets the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + * @param {*} value + */ export function setValueForProperty( - node: Element, - name: string, - value: mixed, - isCustomComponentTag: boolean, -) { +node: Element, +name: string, +value: unknown, +isCustomComponentTag: boolean) +{ const propertyInfo = getPropertyInfo(name); if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) { return; @@ -133,37 +133,37 @@ export function setValueForProperty( if (value === null) { node.removeAttribute(attributeName); } else { - node.setAttribute(attributeName, '' + (value: any)); + node.setAttribute(attributeName, '' + (value as any)); } } return; } - const {mustUseProperty} = propertyInfo; + const { mustUseProperty } = propertyInfo; if (mustUseProperty) { - const {propertyName} = propertyInfo; + const { propertyName } = propertyInfo; if (value === null) { - const {type} = propertyInfo; - (node: any)[propertyName] = type === BOOLEAN ? false : ''; + const { type } = propertyInfo; + (node as any)[propertyName] = type === BOOLEAN ? false : ''; } else { // Contrary to `setAttribute`, object properties are properly // `toString`ed by IE8/9. - (node: any)[propertyName] = value; + (node as any)[propertyName] = value; } return; } // The rest are treated as attributes with special cases. - const {attributeName, attributeNamespace} = propertyInfo; + const { attributeName, attributeNamespace } = propertyInfo; if (value === null) { node.removeAttribute(attributeName); } else { - const {type} = propertyInfo; + const { type } = propertyInfo; let attributeValue; - if (type === BOOLEAN || (type === OVERLOADED_BOOLEAN && value === true)) { + if (type === BOOLEAN || type === OVERLOADED_BOOLEAN && value === true) { attributeValue = ''; } else { // `setAttribute` with objects becomes only `[object]` in IE8/9, // ('' + value) makes it output the correct toString()-value. - attributeValue = '' + (value: any); + attributeValue = '' + (value as any); } if (attributeNamespace) { node.setAttributeNS(attributeNamespace, attributeName, attributeValue); diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js deleted file mode 100644 index 0eac062bed74f..0000000000000 --- a/packages/react-dom/src/client/ReactDOM.js +++ /dev/null @@ -1,893 +0,0 @@ -/** - * 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 {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, - Batch as FiberRootBatch, -} from 'react-reconciler/src/ReactFiberRoot'; - -import '../shared/checkReact'; -import './ReactDOMClientInjection'; - -import { - computeUniqueAsyncExpiration, - findHostInstanceWithNoPortals, - updateContainerAtExpirationTime, - flushRoot, - createContainer, - updateContainer, - batchedUpdates, - unbatchedUpdates, - interactiveUpdates, - flushInteractiveUpdates, - flushSync, - flushControlled, - injectIntoDevTools, - getPublicRootInstance, - findHostInstance, - findHostInstanceWithWarning, -} from 'react-reconciler/inline.dom'; -import {createPortal as createPortalImpl} from 'shared/ReactPortal'; -import {canUseDOM} from 'shared/ExecutionEnvironment'; -import {setBatchingImplementation} from 'events/ReactGenericBatching'; -import { - setRestoreImplementation, - enqueueStateRestore, - restoreStateIfNeeded, -} from 'events/ReactControlledComponent'; -import { - injection as EventPluginHubInjection, - runEventsInBatch, -} from 'events/EventPluginHub'; -import {eventNameDispatchConfigs} from 'events/EventPluginRegistry'; -import { - accumulateTwoPhaseDispatches, - accumulateDirectDispatches, -} from 'events/EventPropagators'; -import {has as hasInstance} from 'shared/ReactInstanceMap'; -import ReactVersion from 'shared/ReactVersion'; -import ReactSharedInternals from 'shared/ReactSharedInternals'; -import getComponentName from 'shared/getComponentName'; -import invariant from 'shared/invariant'; -import lowPriorityWarning from 'shared/lowPriorityWarning'; -import warningWithoutStack from 'shared/warningWithoutStack'; -import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags'; - -import { - getInstanceFromNode, - getNodeFromInstance, - getFiberCurrentPropsFromNode, - getClosestInstanceFromNode, -} from './ReactDOMComponentTree'; -import {restoreControlledState} from './ReactDOMComponent'; -import {dispatchEvent} from '../events/ReactDOMEventListener'; -import { - ELEMENT_NODE, - COMMENT_NODE, - DOCUMENT_NODE, - DOCUMENT_FRAGMENT_NODE, -} from '../shared/HTMLNodeType'; -import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty'; - -const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; - -let topLevelUpdateWarnings; -let warnOnInvalidCallback; -let didWarnAboutUnstableCreatePortal = false; - -if (__DEV__) { - if ( - typeof Map !== 'function' || - // $FlowIssue Flow incorrectly thinks Map has no prototype - Map.prototype == null || - typeof Map.prototype.forEach !== 'function' || - typeof Set !== 'function' || - // $FlowIssue Flow incorrectly thinks Set has no prototype - Set.prototype == null || - typeof Set.prototype.clear !== 'function' || - typeof Set.prototype.forEach !== 'function' - ) { - warningWithoutStack( - false, - 'React depends on Map and Set built-in types. Make sure that you load a ' + - 'polyfill in older browsers. https://fb.me/react-polyfills', - ); - } - - topLevelUpdateWarnings = (container: DOMContainer) => { - if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) { - const hostInstance = findHostInstanceWithNoPortals( - container._reactRootContainer._internalRoot.current, - ); - if (hostInstance) { - warningWithoutStack( - hostInstance.parentNode === container, - 'render(...): It looks like the React-rendered content of this ' + - 'container was removed without using React. This is not ' + - 'supported and will cause errors. Instead, call ' + - 'ReactDOM.unmountComponentAtNode to empty a container.', - ); - } - } - - const isRootRenderedBySomeReact = !!container._reactRootContainer; - const rootEl = getReactRootElementInContainer(container); - const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl)); - - warningWithoutStack( - !hasNonRootReactChild || isRootRenderedBySomeReact, - 'render(...): Replacing React-rendered children with a new root ' + - 'component. If you intended to update the children of this node, ' + - 'you should instead have the existing children update their state ' + - 'and render the new components instead of calling ReactDOM.render.', - ); - - warningWithoutStack( - container.nodeType !== ELEMENT_NODE || - !((container: any): Element).tagName || - ((container: any): Element).tagName.toUpperCase() !== 'BODY', - 'render(): Rendering components directly into 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 rendering into a container element created ' + - 'for your app.', - ); - }; - - warnOnInvalidCallback = function(callback: mixed, callerName: string) { - warningWithoutStack( - callback === null || typeof callback === 'function', - '%s(...): Expected the last optional `callback` argument to be a ' + - 'function. Instead received: %s.', - callerName, - callback, - ); - }; -} - -setRestoreImplementation(restoreControlledState); - -export type DOMContainer = - | (Element & { - _reactRootContainer: ?Root, - _reactHasBeenPassedToCreateRootDEV: ?boolean, - }) - | (Document & { - _reactRootContainer: ?Root, - _reactHasBeenPassedToCreateRootDEV: ?boolean, - }); - -type Batch = FiberRootBatch & { - render(children: ReactNodeList): Work, - then(onComplete: () => mixed): void, - commit(): void, - - // The ReactRoot constructor is hoisted but the prototype methods are not. If - // we move ReactRoot to be above ReactBatch, the inverse error occurs. - // $FlowFixMe Hoisting issue. - _root: Root, - _hasChildren: boolean, - _children: ReactNodeList, - - _callbacks: Array<() => mixed> | null, - _didComplete: boolean, -}; - -type Root = { - render(children: ReactNodeList, callback: ?() => mixed): Work, - unmount(callback: ?() => mixed): Work, - legacy_renderSubtreeIntoContainer( - parentComponent: ?React$Component, - children: ReactNodeList, - callback: ?() => mixed, - ): Work, - createBatch(): Batch, - - _internalRoot: FiberRoot, -}; - -function ReactBatch(root: ReactRoot) { - const expirationTime = computeUniqueAsyncExpiration(); - this._expirationTime = expirationTime; - this._root = root; - this._next = null; - this._callbacks = null; - this._didComplete = false; - this._hasChildren = false; - this._children = null; - this._defer = true; -} -ReactBatch.prototype.render = function(children: ReactNodeList) { - invariant( - this._defer, - 'batch.render: Cannot render a batch that already committed.', - ); - this._hasChildren = true; - this._children = children; - const internalRoot = this._root._internalRoot; - const expirationTime = this._expirationTime; - const work = new ReactWork(); - updateContainerAtExpirationTime( - children, - internalRoot, - null, - expirationTime, - work._onCommit, - ); - return work; -}; -ReactBatch.prototype.then = function(onComplete: () => mixed) { - if (this._didComplete) { - onComplete(); - return; - } - let callbacks = this._callbacks; - if (callbacks === null) { - callbacks = this._callbacks = []; - } - callbacks.push(onComplete); -}; -ReactBatch.prototype.commit = function() { - const internalRoot = this._root._internalRoot; - let firstBatch = internalRoot.firstBatch; - invariant( - this._defer && firstBatch !== null, - 'batch.commit: Cannot commit a batch multiple times.', - ); - - if (!this._hasChildren) { - // This batch is empty. Return. - this._next = null; - this._defer = false; - return; - } - - let expirationTime = this._expirationTime; - - // Ensure this is the first batch in the list. - if (firstBatch !== this) { - // This batch is not the earliest batch. We need to move it to the front. - // Update its expiration time to be the expiration time of the earliest - // batch, so that we can flush it without flushing the other batches. - if (this._hasChildren) { - expirationTime = this._expirationTime = firstBatch._expirationTime; - // Rendering this batch again ensures its children will be the final state - // when we flush (updates are processed in insertion order: last - // update wins). - // TODO: This forces a restart. Should we print a warning? - this.render(this._children); - } - - // Remove the batch from the list. - let previous = null; - let batch = firstBatch; - while (batch !== this) { - previous = batch; - batch = batch._next; - } - invariant( - previous !== null, - 'batch.commit: Cannot commit a batch multiple times.', - ); - previous._next = batch._next; - - // Add it to the front. - this._next = firstBatch; - firstBatch = internalRoot.firstBatch = this; - } - - // Synchronously flush all the work up to this batch's expiration time. - this._defer = false; - flushRoot(internalRoot, expirationTime); - - // Pop the batch from the list. - const next = this._next; - this._next = null; - firstBatch = internalRoot.firstBatch = next; - - // Append the next earliest batch's children to the update queue. - if (firstBatch !== null && firstBatch._hasChildren) { - firstBatch.render(firstBatch._children); - } -}; -ReactBatch.prototype._onComplete = function() { - if (this._didComplete) { - return; - } - this._didComplete = true; - const callbacks = this._callbacks; - if (callbacks === null) { - return; - } - // TODO: Error handling. - for (let i = 0; i < callbacks.length; i++) { - const callback = callbacks[i]; - callback(); - } -}; - -type Work = { - then(onCommit: () => mixed): void, - _onCommit: () => void, - _callbacks: Array<() => mixed> | null, - _didCommit: boolean, -}; - -function ReactWork() { - this._callbacks = null; - this._didCommit = false; - // TODO: Avoid need to bind by replacing callbacks in the update queue with - // list of Work objects. - this._onCommit = this._onCommit.bind(this); -} -ReactWork.prototype.then = function(onCommit: () => mixed): void { - if (this._didCommit) { - onCommit(); - return; - } - let callbacks = this._callbacks; - if (callbacks === null) { - callbacks = this._callbacks = []; - } - callbacks.push(onCommit); -}; -ReactWork.prototype._onCommit = function(): void { - if (this._didCommit) { - return; - } - this._didCommit = true; - const callbacks = this._callbacks; - if (callbacks === null) { - return; - } - // TODO: Error handling. - for (let i = 0; i < callbacks.length; i++) { - const callback = callbacks[i]; - invariant( - typeof callback === 'function', - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: %s', - callback, - ); - callback(); - } -}; - -function ReactRoot( - container: DOMContainer, - isConcurrent: boolean, - hydrate: boolean, -) { - const root = createContainer(container, isConcurrent, hydrate); - this._internalRoot = root; -} -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); - } - updateContainer(children, root, null, work._onCommit); - return work; -}; -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); - } - updateContainer(null, root, null, work._onCommit); - return work; -}; -ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function( - parentComponent: ?React$Component, - 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); - } - updateContainer(children, root, parentComponent, work._onCommit); - return work; -}; -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; -}; - -/** - * True if the supplied DOM node is a valid node element. - * - * @param {?DOMElement} node The candidate DOM node. - * @return {boolean} True if the DOM is a valid DOM node. - * @internal - */ -function isValidContainer(node) { - return !!( - node && - (node.nodeType === ELEMENT_NODE || - node.nodeType === DOCUMENT_NODE || - node.nodeType === DOCUMENT_FRAGMENT_NODE || - (node.nodeType === COMMENT_NODE && - node.nodeValue === ' react-mount-point-unstable ')) - ); -} - -function getReactRootElementInContainer(container: any) { - if (!container) { - return null; - } - - if (container.nodeType === DOCUMENT_NODE) { - return container.documentElement; - } else { - return container.firstChild; - } -} - -function shouldHydrateDueToLegacyHeuristic(container) { - const rootElement = getReactRootElementInContainer(container); - return !!( - rootElement && - rootElement.nodeType === ELEMENT_NODE && - rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME) - ); -} - -setBatchingImplementation( - batchedUpdates, - interactiveUpdates, - flushInteractiveUpdates, -); - -let warnedAboutHydrateAPI = false; - -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)) { - if (__DEV__) { - if ( - !warned && - rootSibling.nodeType === ELEMENT_NODE && - (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME) - ) { - warned = true; - warningWithoutStack( - false, - 'render(): Target node has markup rendered by React, but there ' + - 'are unrelated nodes as well. This is most commonly caused by ' + - 'white-space inserted around server-rendered markup.', - ); - } - } - container.removeChild(rootSibling); - } - } - if (__DEV__) { - if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) { - warnedAboutHydrateAPI = true; - lowPriorityWarning( - false, - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + - 'will stop working in React v17. Replace the ReactDOM.render() call ' + - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.', - ); - } - } - // Legacy roots are not async by default. - const isConcurrent = false; - return new ReactRoot(container, isConcurrent, shouldHydrate); -} - -function legacyRenderSubtreeIntoContainer( - parentComponent: ?React$Component, - children: ReactNodeList, - container: DOMContainer, - forceHydrate: boolean, - callback: ?Function, -) { - if (__DEV__) { - topLevelUpdateWarnings(container); - } - - // TODO: Without `any` type, Flow says "Property cannot be accessed on any - // member of intersection type." Whyyyyyy. - 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 = getPublicRootInstance(root._internalRoot); - originalCallback.call(instance); - }; - } - // Initial mount should not be batched. - 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 = getPublicRootInstance(root._internalRoot); - originalCallback.call(instance); - }; - } - // Update - if (parentComponent != null) { - root.legacy_renderSubtreeIntoContainer( - parentComponent, - children, - callback, - ); - } else { - root.render(children, callback); - } - } - return getPublicRootInstance(root._internalRoot); -} - -function createPortal( - children: ReactNodeList, - container: DOMContainer, - key: ?string = null, -) { - invariant( - isValidContainer(container), - 'Target container is not a DOM element.', - ); - // TODO: pass ReactDOM portal implementation as third argument - return createPortalImpl(children, container, null, key); -} - -const ReactDOM: Object = { - createPortal, - - findDOMNode( - componentOrElement: Element | ?React$Component, - ): null | Element | Text { - if (__DEV__) { - let owner = (ReactCurrentOwner.current: any); - if (owner !== null && owner.stateNode !== null) { - const warnedAboutRefsInRender = - owner.stateNode._warnedAboutRefsInRender; - warningWithoutStack( - warnedAboutRefsInRender, - '%s is accessing findDOMNode inside its render(). ' + - 'render() should be a pure function of props and state. It should ' + - 'never access something that requires stale data from the previous ' + - 'render, such as refs. Move this logic to componentDidMount and ' + - 'componentDidUpdate instead.', - getComponentName(owner.type) || 'A component', - ); - owner.stateNode._warnedAboutRefsInRender = true; - } - } - if (componentOrElement == null) { - return null; - } - if ((componentOrElement: any).nodeType === ELEMENT_NODE) { - return (componentOrElement: any); - } - if (__DEV__) { - return findHostInstanceWithWarning(componentOrElement, 'findDOMNode'); - } - return findHostInstance(componentOrElement); - }, - - hydrate(element: React$Node, container: DOMContainer, callback: ?Function) { - invariant( - isValidContainer(container), - 'Target container is not a DOM element.', - ); - if (__DEV__) { - warningWithoutStack( - !container._reactHasBeenPassedToCreateRootDEV, - 'You are calling ReactDOM.hydrate() on a container that was previously ' + - 'passed to ReactDOM.%s(). This is not supported. ' + - 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', - ); - } - // TODO: throw or warn if we couldn't hydrate? - return legacyRenderSubtreeIntoContainer( - null, - element, - container, - true, - callback, - ); - }, - - render( - element: React$Element, - container: DOMContainer, - callback: ?Function, - ) { - invariant( - isValidContainer(container), - 'Target container is not a DOM element.', - ); - if (__DEV__) { - warningWithoutStack( - !container._reactHasBeenPassedToCreateRootDEV, - 'You are calling ReactDOM.render() on a container that was previously ' + - 'passed to ReactDOM.%s(). This is not supported. ' + - 'Did you mean to call root.render(element)?', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', - ); - } - return legacyRenderSubtreeIntoContainer( - null, - element, - container, - false, - callback, - ); - }, - - unstable_renderSubtreeIntoContainer( - parentComponent: React$Component, - element: React$Element, - containerNode: DOMContainer, - callback: ?Function, - ) { - invariant( - isValidContainer(containerNode), - 'Target container is not a DOM element.', - ); - invariant( - parentComponent != null && hasInstance(parentComponent), - 'parentComponent must be a valid React Component', - ); - return legacyRenderSubtreeIntoContainer( - parentComponent, - element, - containerNode, - false, - callback, - ); - }, - - unmountComponentAtNode(container: DOMContainer) { - invariant( - isValidContainer(container), - 'unmountComponentAtNode(...): Target container is not a DOM element.', - ); - - if (__DEV__) { - warningWithoutStack( - !container._reactHasBeenPassedToCreateRootDEV, - 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' + - 'passed to ReactDOM.%s(). This is not supported. Did you mean to call root.unmount()?', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', - ); - } - - if (container._reactRootContainer) { - if (__DEV__) { - const rootEl = getReactRootElementInContainer(container); - const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl); - warningWithoutStack( - !renderedByDifferentReact, - "unmountComponentAtNode(): The node you're attempting to unmount " + - 'was rendered by another copy of React.', - ); - } - - // Unmount should not be batched. - unbatchedUpdates(() => { - legacyRenderSubtreeIntoContainer(null, null, container, false, () => { - container._reactRootContainer = null; - }); - }); - // If you call unmountComponentAtNode twice in quick succession, you'll - // get `true` twice. That's probably fine? - return true; - } else { - if (__DEV__) { - const rootEl = getReactRootElementInContainer(container); - const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl)); - - // Check if the container itself is a React root node. - const isContainerReactRoot = - container.nodeType === ELEMENT_NODE && - isValidContainer(container.parentNode) && - !!container.parentNode._reactRootContainer; - - warningWithoutStack( - !hasNonRootReactChild, - "unmountComponentAtNode(): The node you're attempting to unmount " + - 'was rendered by React and is not a top-level container. %s', - isContainerReactRoot - ? 'You may have accidentally passed in a React root node instead ' + - 'of its container.' - : 'Instead, have the parent component update its state and ' + - 'rerender in order to remove this component.', - ); - } - - return false; - } - }, - - // Temporary alias since we already shipped React 16 RC with it. - // TODO: remove in React 17. - unstable_createPortal(...args) { - if (!didWarnAboutUnstableCreatePortal) { - didWarnAboutUnstableCreatePortal = true; - lowPriorityWarning( - false, - 'The ReactDOM.unstable_createPortal() alias has been deprecated, ' + - 'and will be removed in React 17+. Update your code to use ' + - 'ReactDOM.createPortal() instead. It has the exact same API, ' + - 'but without the "unstable_" prefix.', - ); - } - return createPortal(...args); - }, - - unstable_batchedUpdates: batchedUpdates, - - unstable_interactiveUpdates: interactiveUpdates, - - flushSync: flushSync, - - unstable_createRoot: createRoot, - unstable_flushControlled: flushControlled, - - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { - // Keep in sync with ReactDOMUnstableNativeDependencies.js - // and ReactTestUtils.js. This is an array for better minification. - Events: [ - getInstanceFromNode, - getNodeFromInstance, - getFiberCurrentPropsFromNode, - EventPluginHubInjection.injectEventPluginsByName, - eventNameDispatchConfigs, - accumulateTwoPhaseDispatches, - accumulateDirectDispatches, - enqueueStateRestore, - restoreStateIfNeeded, - dispatchEvent, - runEventsInBatch, - ], - }, -}; - -type RootOptions = { - hydrate?: boolean, -}; - -function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot { - const functionName = enableStableConcurrentModeAPIs - ? 'createRoot' - : 'unstable_createRoot'; - invariant( - isValidContainer(container), - '%s(...): Target container is not a DOM element.', - functionName, - ); - if (__DEV__) { - warningWithoutStack( - !container._reactRootContainer, - 'You are calling ReactDOM.%s() on a container that was previously ' + - 'passed to ReactDOM.render(). This is not supported.', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', - ); - container._reactHasBeenPassedToCreateRootDEV = true; - } - const hydrate = options != null && options.hydrate === true; - return new ReactRoot(container, true, hydrate); -} - -if (enableStableConcurrentModeAPIs) { - ReactDOM.createRoot = createRoot; - ReactDOM.unstable_createRoot = undefined; -} - -const foundDevTools = injectIntoDevTools({ - findFiberByHostInstance: getClosestInstanceFromNode, - bundleType: __DEV__ ? 1 : 0, - version: ReactVersion, - rendererPackageName: 'react-dom', -}); - -if (__DEV__) { - if (!foundDevTools && canUseDOM && window.top === window.self) { - // If we're in Chrome or Firefox, provide a download link if not installed. - if ( - (navigator.userAgent.indexOf('Chrome') > -1 && - navigator.userAgent.indexOf('Edge') === -1) || - navigator.userAgent.indexOf('Firefox') > -1 - ) { - const protocol = window.location.protocol; - // Don't warn in exotic cases like chrome-extension://. - if (/^(https?|file):$/.test(protocol)) { - console.info( - '%cDownload the React DevTools ' + - 'for a better development experience: ' + - 'https://fb.me/react-devtools' + - (protocol === 'file:' - ? '\nYou might need to use a local HTTP server (instead of file://): ' + - 'https://fb.me/react-devtools-faq' - : ''), - 'font-weight:bold', - ); - } - } - } -} - -export default ReactDOM; diff --git a/packages/react-dom/src/client/ReactDOM.ts b/packages/react-dom/src/client/ReactDOM.ts new file mode 100644 index 0000000000000..b166d348506d4 --- /dev/null +++ b/packages/react-dom/src/client/ReactDOM.ts @@ -0,0 +1,908 @@ +/** + * 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 { ReactNodeList } from 'shared/ReactTypes'; +// TODO: This type is shared between the reconciler and ReactDOM, but will +// eventually be lifted out to the renderer. +import { +FiberRoot, +Batch as FiberRootBatch } from +'react-reconciler/src/ReactFiberRoot'; + +import '../shared/checkReact'; +import './ReactDOMClientInjection'; + +import { +computeUniqueAsyncExpiration, +findHostInstanceWithNoPortals, +updateContainerAtExpirationTime, +flushRoot, +createContainer, +updateContainer, +batchedUpdates, +unbatchedUpdates, +interactiveUpdates, +flushInteractiveUpdates, +flushSync, +flushControlled, +injectIntoDevTools, +getPublicRootInstance, +findHostInstance, +findHostInstanceWithWarning } from +'react-reconciler/inline.dom'; +import { createPortal as createPortalImpl } from 'shared/ReactPortal'; +import { canUseDOM } from 'shared/ExecutionEnvironment'; +import { setBatchingImplementation } from 'events/ReactGenericBatching'; +import { +setRestoreImplementation, +enqueueStateRestore, +restoreStateIfNeeded } from +'events/ReactControlledComponent'; +import { +injection as EventPluginHubInjection, +runEventsInBatch } from +'events/EventPluginHub'; +import { eventNameDispatchConfigs } from 'events/EventPluginRegistry'; +import { +accumulateTwoPhaseDispatches, +accumulateDirectDispatches } from +'events/EventPropagators'; +import { has as hasInstance } from 'shared/ReactInstanceMap'; +import ReactVersion from 'shared/ReactVersion'; +import ReactSharedInternals from 'shared/ReactSharedInternals'; +import getComponentName from 'shared/getComponentName'; +import invariant from 'shared/invariant'; +import lowPriorityWarning from 'shared/lowPriorityWarning'; +import warningWithoutStack from 'shared/warningWithoutStack'; +import { enableStableConcurrentModeAPIs } from 'shared/ReactFeatureFlags'; + +import { +getInstanceFromNode, +getNodeFromInstance, +getFiberCurrentPropsFromNode, +getClosestInstanceFromNode } from +'./ReactDOMComponentTree'; +import { restoreControlledState } from './ReactDOMComponent'; +import { dispatchEvent } from '../events/ReactDOMEventListener'; +import { +ELEMENT_NODE, +COMMENT_NODE, +DOCUMENT_NODE, +DOCUMENT_FRAGMENT_NODE } from +'../shared/HTMLNodeType'; +import { ROOT_ATTRIBUTE_NAME } from '../shared/DOMProperty'; + +const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; + +let topLevelUpdateWarnings; +let warnOnInvalidCallback; +let didWarnAboutUnstableCreatePortal = false; + +if (__DEV__) { + if ( + typeof Map !== 'function' || + // $FlowIssue Flow incorrectly thinks Map has no prototype + Map.prototype == null || + typeof Map.prototype.forEach !== 'function' || + typeof Set !== 'function' || + // $FlowIssue Flow incorrectly thinks Set has no prototype + Set.prototype == null || + typeof Set.prototype.clear !== 'function' || + typeof Set.prototype.forEach !== 'function') + { + warningWithoutStack( + false, + 'React depends on Map and Set built-in types. Make sure that you load a ' + + 'polyfill in older browsers. https://fb.me/react-polyfills'); + + } + + topLevelUpdateWarnings = (container: DOMContainer) => { + if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) { + const hostInstance = findHostInstanceWithNoPortals( + container._reactRootContainer._internalRoot.current); + + if (hostInstance) { + warningWithoutStack( + hostInstance.parentNode === container, + 'render(...): It looks like the React-rendered content of this ' + + 'container was removed without using React. This is not ' + + 'supported and will cause errors. Instead, call ' + + 'ReactDOM.unmountComponentAtNode to empty a container.'); + + } + } + + const isRootRenderedBySomeReact = !!container._reactRootContainer; + const rootEl = getReactRootElementInContainer(container); + const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl)); + + warningWithoutStack( + !hasNonRootReactChild || isRootRenderedBySomeReact, + 'render(...): Replacing React-rendered children with a new root ' + + 'component. If you intended to update the children of this node, ' + + 'you should instead have the existing children update their state ' + + 'and render the new components instead of calling ReactDOM.render.'); + + + warningWithoutStack( + container.nodeType !== ELEMENT_NODE || + !((container as any) as Element).tagName || + ((container as any) as Element).tagName.toUpperCase() !== 'BODY', + 'render(): Rendering components directly into 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 rendering into a container element created ' + + 'for your app.'); + + }; + + warnOnInvalidCallback = function (callback: unknown, callerName: string) { + warningWithoutStack( + callback === null || typeof callback === 'function', + '%s(...): Expected the last optional `callback` argument to be a ' + + 'function. Instead received: %s.', + callerName, + callback); + + }; +} + +setRestoreImplementation(restoreControlledState); + +export type DOMContainer = +(Element & { + _reactRootContainer: Root | null | undefined; + _reactHasBeenPassedToCreateRootDEV: boolean | null | undefined;}) | ( + +Document & { + _reactRootContainer: Root | null | undefined; + _reactHasBeenPassedToCreateRootDEV: boolean | null | undefined;}); + + +type Batch = FiberRootBatch & { + render: (children: ReactNodeList) => Work; + then: (onComplete: () => unknown) => void; + commit: () => void; + + // The ReactRoot constructor is hoisted but the prototype methods are not. If + // we move ReactRoot to be above ReactBatch, the inverse error occurs. + // $FlowFixMe Hoisting issue. + _root: Root; + _hasChildren: boolean; + _children: ReactNodeList; + + _callbacks: Array<() => unknown> | null; + _didComplete: boolean;}; + + +type Root = { + render: (children: ReactNodeList, callback: () => unknown | null | undefined) => Work; + unmount: (callback: () => unknown | null | undefined) => Work; + legacy_renderSubtreeIntoContainer: (parentComponent: + React$Component | null | undefined, children: + ReactNodeList, callback: + () => unknown | null | undefined) => + Work; + createBatch: () => Batch; + + _internalRoot: FiberRoot;}; + + +function ReactBatch(root: ReactRoot) { + const expirationTime = computeUniqueAsyncExpiration(); + this._expirationTime = expirationTime; + this._root = root; + this._next = null; + this._callbacks = null; + this._didComplete = false; + this._hasChildren = false; + this._children = null; + this._defer = true; +} +ReactBatch.prototype.render = function (children: ReactNodeList) { + invariant( + this._defer, + 'batch.render: Cannot render a batch that already committed.'); + + this._hasChildren = true; + this._children = children; + const internalRoot = this._root._internalRoot; + const expirationTime = this._expirationTime; + const work = new ReactWork(); + updateContainerAtExpirationTime( + children, + internalRoot, + null, + expirationTime, + work._onCommit); + + return work; +}; +ReactBatch.prototype.then = function (onComplete: () => unknown) { + if (this._didComplete) { + onComplete(); + return; + } + let callbacks = this._callbacks; + if (callbacks === null) { + callbacks = this._callbacks = []; + } + callbacks.push(onComplete); +}; +ReactBatch.prototype.commit = function () { + const internalRoot = this._root._internalRoot; + let firstBatch = internalRoot.firstBatch; + invariant( + this._defer && firstBatch !== null, + 'batch.commit: Cannot commit a batch multiple times.'); + + + if (!this._hasChildren) { + // This batch is empty. Return. + this._next = null; + this._defer = false; + return; + } + + let expirationTime = this._expirationTime; + + // Ensure this is the first batch in the list. + if (firstBatch !== this) { + // This batch is not the earliest batch. We need to move it to the front. + // Update its expiration time to be the expiration time of the earliest + // batch, so that we can flush it without flushing the other batches. + if (this._hasChildren) { + expirationTime = this._expirationTime = firstBatch._expirationTime; + // Rendering this batch again ensures its children will be the final state + // when we flush (updates are processed in insertion order: last + // update wins). + // TODO: This forces a restart. Should we print a warning? + this.render(this._children); + } + + // Remove the batch from the list. + let previous = null; + let batch = firstBatch; + while (batch !== this) { + previous = batch; + batch = batch._next; + } + invariant( + previous !== null, + 'batch.commit: Cannot commit a batch multiple times.'); + + previous._next = batch._next; + + // Add it to the front. + this._next = firstBatch; + firstBatch = internalRoot.firstBatch = this; + } + + // Synchronously flush all the work up to this batch's expiration time. + this._defer = false; + flushRoot(internalRoot, expirationTime); + + // Pop the batch from the list. + const next = this._next; + this._next = null; + firstBatch = internalRoot.firstBatch = next; + + // Append the next earliest batch's children to the update queue. + if (firstBatch !== null && firstBatch._hasChildren) { + firstBatch.render(firstBatch._children); + } +}; +ReactBatch.prototype._onComplete = function () { + if (this._didComplete) { + return; + } + this._didComplete = true; + const callbacks = this._callbacks; + if (callbacks === null) { + return; + } + // TODO: Error handling. + for (let i = 0; i < callbacks.length; i++) { + const callback = callbacks[i]; + callback(); + } +}; + +type Work = { + then: (onCommit: () => unknown) => void; + _onCommit: () => void; + _callbacks: Array<() => unknown> | null; + _didCommit: boolean;}; + + +/** + * 在 this._onCommit 被调用时,执行 this.then 传入的所有 callback + * + */ +function ReactWork() { + this._callbacks = null; + this._didCommit = false; + // TODO: Avoid need to bind by replacing callbacks in the update queue with + // list of Work objects. + this._onCommit = this._onCommit.bind(this); +} +ReactWork.prototype.then = function (onCommit: () => unknown): undefined { + if (this._didCommit) { + onCommit(); + return; + } + let callbacks = this._callbacks; + if (callbacks === null) { + callbacks = this._callbacks = []; + } + callbacks.push(onCommit); +}; +ReactWork.prototype._onCommit = function (): undefined { + if (this._didCommit) { + return; + } + this._didCommit = true; + const callbacks = this._callbacks; + if (callbacks === null) { + return; + } + // TODO: Error handling. + for (let i = 0; i < callbacks.length; i++) { + const callback = callbacks[i]; + invariant( + typeof callback === 'function', + 'Invalid argument passed as callback. Expected a function. Instead ' + + 'received: %s', + callback); + + callback(); + } +}; + +function ReactRoot(container: +DOMContainer, isConcurrent: +boolean, hydrate: +boolean) +{ + const root = createContainer(container, isConcurrent, hydrate); + this._internalRoot = root; +} +ReactRoot.prototype.render = function ( +children: ReactNodeList, +callback: () => unknown | null | undefined) +: Work { + const root = this._internalRoot; + const work = new ReactWork(); + callback = callback === undefined ? null : callback; + if (__DEV__) { + warnOnInvalidCallback(callback, 'render'); + } + if (callback !== null) { + // callback 是 ReactDOM.render 的第三个参数 + work.then(callback); + } + updateContainer(children, root, null, work._onCommit); + return work; +}; +ReactRoot.prototype.unmount = function (callback: () => unknown | null | undefined): 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); + } + updateContainer(null, root, null, work._onCommit); + return work; +}; +ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function ( +parentComponent: React$Component | null | undefined, +children: ReactNodeList, +callback: () => unknown | null | undefined) +: 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); + } + updateContainer(children, root, parentComponent, work._onCommit); + return work; +}; +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; +}; + +/** + * True if the supplied DOM node is a valid node element. + * + * @param {?DOMElement} node The candidate DOM node. + * @return {boolean} True if the DOM is a valid DOM node. + * @internal + */ +function isValidContainer(node) { + return !!( + node && ( + node.nodeType === ELEMENT_NODE || + node.nodeType === DOCUMENT_NODE || + node.nodeType === DOCUMENT_FRAGMENT_NODE || + node.nodeType === COMMENT_NODE && + node.nodeValue === ' react-mount-point-unstable ')); + +} + +function getReactRootElementInContainer(container: any) { + if (!container) { + return null; + } + + if (container.nodeType === DOCUMENT_NODE) { + return container.documentElement; + } else { + return container.firstChild; + } +} + +function shouldHydrateDueToLegacyHeuristic(container) { + const rootElement = getReactRootElementInContainer(container); + return !!( + rootElement && + rootElement.nodeType === ELEMENT_NODE && + rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)); + +} + +setBatchingImplementation( +batchedUpdates, +interactiveUpdates, +flushInteractiveUpdates); + + +let warnedAboutHydrateAPI = false; + +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) { + if (__DEV__) { + if ( + !warned && + rootSibling.nodeType === ELEMENT_NODE && + (rootSibling as any).hasAttribute(ROOT_ATTRIBUTE_NAME)) + { + warned = true; + warningWithoutStack( + false, + 'render(): Target node has markup rendered by React, but there ' + + 'are unrelated nodes as well. This is most commonly caused by ' + + 'white-space inserted around server-rendered markup.'); + + } + } + container.removeChild(rootSibling); + } + } + if (__DEV__) { + if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) { + warnedAboutHydrateAPI = true; + lowPriorityWarning( + false, + 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + + 'will stop working in React v17. Replace the ReactDOM.render() call ' + + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.'); + + } + } + // Legacy roots are not async by default. + const isConcurrent = false; + return new ReactRoot(container, isConcurrent, shouldHydrate); +} + +/** + * + * + * @param {?React$Component} parentComponent + * @param {ReactNodeList} children + * @param {DOMContainer} container + * @param {boolean} forceHydrate + * @param {?Function} callback + * @returns + */ +function legacyRenderSubtreeIntoContainer(parentComponent: +React$Component | null | undefined, children: +ReactNodeList, container: +DOMContainer, forceHydrate: +boolean, callback: +Function | null | undefined) +{ + if (__DEV__) { + topLevelUpdateWarnings(container); + } + + // TODO: Without `any` type, Flow says "Property cannot be accessed on any + // member of intersection type." Whyyyyyy. + let root: Root = (container._reactRootContainer as any); + if (!root) { + // Initial mount + root = container._reactRootContainer = legacyCreateRootFromDOMContainer( + container, + forceHydrate); + + if (typeof callback === 'function') { + const originalCallback = callback; + callback = function () { + const instance = getPublicRootInstance(root._internalRoot); + originalCallback.call(instance); + }; + } + // Initial mount should not be batched. + 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 = getPublicRootInstance(root._internalRoot); + originalCallback.call(instance); + }; + } + // Update + if (parentComponent != null) { + root.legacy_renderSubtreeIntoContainer( + parentComponent, + children, + callback); + + } else { + root.render(children, callback); + } + } + return getPublicRootInstance(root._internalRoot); +} + +function createPortal(children: +ReactNodeList, container: +DOMContainer, +key: string | null | undefined = null) +{ + invariant( + isValidContainer(container), + 'Target container is not a DOM element.'); + + // TODO: pass ReactDOM portal implementation as third argument + return createPortalImpl(children, container, null, key); +} + +const ReactDOM: Object = { + createPortal, + + findDOMNode( + componentOrElement: Element | (React$Component | null | undefined)) + : null | Element | Text { + if (__DEV__) { + let owner = (ReactCurrentOwner.current as any); + if (owner !== null && owner.stateNode !== null) { + const warnedAboutRefsInRender = + owner.stateNode._warnedAboutRefsInRender; + warningWithoutStack( + warnedAboutRefsInRender, + '%s is accessing findDOMNode inside its render(). ' + + 'render() should be a pure function of props and state. It should ' + + 'never access something that requires stale data from the previous ' + + 'render, such as refs. Move this logic to componentDidMount and ' + + 'componentDidUpdate instead.', + getComponentName(owner.type) || 'A component'); + + owner.stateNode._warnedAboutRefsInRender = true; + } + } + if (componentOrElement == null) { + return null; + } + if ((componentOrElement as any).nodeType === ELEMENT_NODE) { + return (componentOrElement as any); + } + if (__DEV__) { + return findHostInstanceWithWarning(componentOrElement, 'findDOMNode'); + } + return findHostInstance(componentOrElement); + }, + + hydrate(element: React$Node, container: DOMContainer, callback: Function | null | undefined) { + invariant( + isValidContainer(container), + 'Target container is not a DOM element.'); + + if (__DEV__) { + warningWithoutStack( + !container._reactHasBeenPassedToCreateRootDEV, + 'You are calling ReactDOM.hydrate() on a container that was previously ' + + 'passed to ReactDOM.%s(). This is not supported. ' + + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?', + enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot'); + + } + // TODO: throw or warn if we couldn't hydrate? + return legacyRenderSubtreeIntoContainer( + null, + element, + container, + true, + callback); + + }, + + render( + element: React$Element, + container: DOMContainer, + callback: Function | null | undefined) + { + invariant( + isValidContainer(container), + 'Target container is not a DOM element.'); + + if (__DEV__) { + warningWithoutStack( + !container._reactHasBeenPassedToCreateRootDEV, + 'You are calling ReactDOM.render() on a container that was previously ' + + 'passed to ReactDOM.%s(). This is not supported. ' + + 'Did you mean to call root.render(element)?', + enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot'); + + } + return legacyRenderSubtreeIntoContainer( + null, + element, + container, + false, + callback); + + }, + + unstable_renderSubtreeIntoContainer( + parentComponent: React$Component, + element: React$Element, + containerNode: DOMContainer, + callback: Function | null | undefined) + { + invariant( + isValidContainer(containerNode), + 'Target container is not a DOM element.'); + + invariant( + parentComponent != null && hasInstance(parentComponent), + 'parentComponent must be a valid React Component'); + + return legacyRenderSubtreeIntoContainer( + parentComponent, + element, + containerNode, + false, + callback); + + }, + + unmountComponentAtNode(container: DOMContainer) { + invariant( + isValidContainer(container), + 'unmountComponentAtNode(...): Target container is not a DOM element.'); + + + if (__DEV__) { + warningWithoutStack( + !container._reactHasBeenPassedToCreateRootDEV, + 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' + + 'passed to ReactDOM.%s(). This is not supported. Did you mean to call root.unmount()?', + enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot'); + + } + + if (container._reactRootContainer) { + if (__DEV__) { + const rootEl = getReactRootElementInContainer(container); + const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl); + warningWithoutStack( + !renderedByDifferentReact, + "unmountComponentAtNode(): The node you're attempting to unmount " + + 'was rendered by another copy of React.'); + + } + + // Unmount should not be batched. + unbatchedUpdates(() => { + legacyRenderSubtreeIntoContainer(null, null, container, false, () => { + container._reactRootContainer = null; + }); + }); + // If you call unmountComponentAtNode twice in quick succession, you'll + // get `true` twice. That's probably fine? + return true; + } else { + if (__DEV__) { + const rootEl = getReactRootElementInContainer(container); + const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl)); + + // Check if the container itself is a React root node. + const isContainerReactRoot = + container.nodeType === ELEMENT_NODE && + isValidContainer(container.parentNode) && + !!container.parentNode._reactRootContainer; + + warningWithoutStack( + !hasNonRootReactChild, + "unmountComponentAtNode(): The node you're attempting to unmount " + + 'was rendered by React and is not a top-level container. %s', + isContainerReactRoot ? + 'You may have accidentally passed in a React root node instead ' + + 'of its container.' : + 'Instead, have the parent component update its state and ' + + 'rerender in order to remove this component.'); + + } + + return false; + } + }, + + // Temporary alias since we already shipped React 16 RC with it. + // TODO: remove in React 17. + unstable_createPortal(...args) { + if (!didWarnAboutUnstableCreatePortal) { + didWarnAboutUnstableCreatePortal = true; + lowPriorityWarning( + false, + 'The ReactDOM.unstable_createPortal() alias has been deprecated, ' + + 'and will be removed in React 17+. Update your code to use ' + + 'ReactDOM.createPortal() instead. It has the exact same API, ' + + 'but without the "unstable_" prefix.'); + + } + return createPortal(...args); + }, + + unstable_batchedUpdates: batchedUpdates, + + unstable_interactiveUpdates: interactiveUpdates, + + flushSync: flushSync, + + unstable_createRoot: createRoot, + unstable_flushControlled: flushControlled, + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { + // Keep in sync with ReactDOMUnstableNativeDependencies.js + // and ReactTestUtils.js. This is an array for better minification. + Events: [ + getInstanceFromNode, + getNodeFromInstance, + getFiberCurrentPropsFromNode, + EventPluginHubInjection.injectEventPluginsByName, + eventNameDispatchConfigs, + accumulateTwoPhaseDispatches, + accumulateDirectDispatches, + enqueueStateRestore, + restoreStateIfNeeded, + dispatchEvent, + runEventsInBatch] } }; + + + + +type RootOptions = { + hydrate?: boolean;}; + + +function createRoot(container: DOMContainer, options: RootOptions): ReactRoot { + const functionName = enableStableConcurrentModeAPIs ? + 'createRoot' : + 'unstable_createRoot'; + invariant( + isValidContainer(container), + '%s(...): Target container is not a DOM element.', + functionName); + + if (__DEV__) { + warningWithoutStack( + !container._reactRootContainer, + 'You are calling ReactDOM.%s() on a container that was previously ' + + 'passed to ReactDOM.render(). This is not supported.', + enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot'); + + container._reactHasBeenPassedToCreateRootDEV = true; + } + const hydrate = options != null && options.hydrate === true; + return new ReactRoot(container, true, hydrate); +} + +if (enableStableConcurrentModeAPIs) { + ReactDOM.createRoot = createRoot; + ReactDOM.unstable_createRoot = undefined; +} + +const foundDevTools = injectIntoDevTools({ + findFiberByHostInstance: getClosestInstanceFromNode, + bundleType: __DEV__ ? 1 : 0, + version: ReactVersion, + rendererPackageName: 'react-dom' }); + + +if (__DEV__) { + if (!foundDevTools && canUseDOM && window.top === window.self) { + // If we're in Chrome or Firefox, provide a download link if not installed. + if ( + navigator.userAgent.indexOf('Chrome') > -1 && + navigator.userAgent.indexOf('Edge') === -1 || + navigator.userAgent.indexOf('Firefox') > -1) + { + const protocol = window.location.protocol; + // Don't warn in exotic cases like chrome-extension://. + if (/^(https?|file):$/.test(protocol)) { + console.info( + '%cDownload the React DevTools ' + + 'for a better development experience: ' + + 'https://fb.me/react-devtools' + ( + protocol === 'file:' ? + '\nYou might need to use a local HTTP server (instead of file://): ' + + 'https://fb.me/react-devtools-faq' : + ''), + 'font-weight:bold'); + + } + } + } +} + +export default ReactDOM; diff --git a/packages/react-dom/src/client/ReactDOMClientInjection.js b/packages/react-dom/src/client/ReactDOMClientInjection.ts similarity index 55% rename from packages/react-dom/src/client/ReactDOMClientInjection.js rename to packages/react-dom/src/client/ReactDOMClientInjection.ts index c2c6229e05656..c08fe362b98c8 100644 --- a/packages/react-dom/src/client/ReactDOMClientInjection.js +++ b/packages/react-dom/src/client/ReactDOMClientInjection.ts @@ -5,14 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import {injection as EventPluginHubInjection} from 'events/EventPluginHub'; -import {setComponentTree} from 'events/EventPluginUtils'; +import { injection as EventPluginHubInjection } from 'events/EventPluginHub'; +import { setComponentTree } from 'events/EventPluginUtils'; import { - getFiberCurrentPropsFromNode, - getInstanceFromNode, - getNodeFromInstance, -} from './ReactDOMComponentTree'; +getFiberCurrentPropsFromNode, +getInstanceFromNode, +getNodeFromInstance } from +'./ReactDOMComponentTree'; import BeforeInputEventPlugin from '../events/BeforeInputEventPlugin'; import ChangeEventPlugin from '../events/ChangeEventPlugin'; import DOMEventPluginOrder from '../events/DOMEventPluginOrder'; @@ -21,23 +21,22 @@ import SelectEventPlugin from '../events/SelectEventPlugin'; import SimpleEventPlugin from '../events/SimpleEventPlugin'; /** - * Inject modules for resolving DOM hierarchy and plugin ordering. - */ + * Inject modules for resolving DOM hierarchy and plugin ordering. + */ EventPluginHubInjection.injectEventPluginOrder(DOMEventPluginOrder); setComponentTree( - getFiberCurrentPropsFromNode, - getInstanceFromNode, - getNodeFromInstance, -); +getFiberCurrentPropsFromNode, +getInstanceFromNode, +getNodeFromInstance); + /** - * Some important event plugins included by default (without having to require - * them). - */ + * Some important event plugins included by default (without having to require + * them). + */ EventPluginHubInjection.injectEventPluginsByName({ SimpleEventPlugin: SimpleEventPlugin, EnterLeaveEventPlugin: EnterLeaveEventPlugin, ChangeEventPlugin: ChangeEventPlugin, SelectEventPlugin: SelectEventPlugin, - BeforeInputEventPlugin: BeforeInputEventPlugin, -}); + BeforeInputEventPlugin: BeforeInputEventPlugin }); diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.ts similarity index 75% rename from packages/react-dom/src/client/ReactDOMComponent.js rename to packages/react-dom/src/client/ReactDOMComponent.ts index fa8ee0a0acfb0..9d2e0a1fe36a1 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.ts @@ -8,75 +8,75 @@ */ // TODO: direct imports like some-package/src/* are bad. Fix me. -import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber'; -import {registrationNameModules} from 'events/EventPluginRegistry'; +import { getCurrentFiberOwnerNameInDevOrNull } from 'react-reconciler/src/ReactCurrentFiber'; +import { registrationNameModules } from 'events/EventPluginRegistry'; import warning from 'shared/warning'; -import {canUseDOM} from 'shared/ExecutionEnvironment'; +import { canUseDOM } from 'shared/ExecutionEnvironment'; import warningWithoutStack from 'shared/warningWithoutStack'; import { - getValueForAttribute, - getValueForProperty, - setValueForProperty, -} from './DOMPropertyOperations'; +getValueForAttribute, +getValueForProperty, +setValueForProperty } from +'./DOMPropertyOperations'; import { - initWrapperState as ReactDOMInputInitWrapperState, - getHostProps as ReactDOMInputGetHostProps, - postMountWrapper as ReactDOMInputPostMountWrapper, - updateChecked as ReactDOMInputUpdateChecked, - updateWrapper as ReactDOMInputUpdateWrapper, - restoreControlledState as ReactDOMInputRestoreControlledState, -} from './ReactDOMInput'; +initWrapperState as ReactDOMInputInitWrapperState, +getHostProps as ReactDOMInputGetHostProps, +postMountWrapper as ReactDOMInputPostMountWrapper, +updateChecked as ReactDOMInputUpdateChecked, +updateWrapper as ReactDOMInputUpdateWrapper, +restoreControlledState as ReactDOMInputRestoreControlledState } from +'./ReactDOMInput'; import { - getHostProps as ReactDOMOptionGetHostProps, - postMountWrapper as ReactDOMOptionPostMountWrapper, - validateProps as ReactDOMOptionValidateProps, -} from './ReactDOMOption'; +getHostProps as ReactDOMOptionGetHostProps, +postMountWrapper as ReactDOMOptionPostMountWrapper, +validateProps as ReactDOMOptionValidateProps } from +'./ReactDOMOption'; import { - initWrapperState as ReactDOMSelectInitWrapperState, - getHostProps as ReactDOMSelectGetHostProps, - postMountWrapper as ReactDOMSelectPostMountWrapper, - restoreControlledState as ReactDOMSelectRestoreControlledState, - postUpdateWrapper as ReactDOMSelectPostUpdateWrapper, -} from './ReactDOMSelect'; +initWrapperState as ReactDOMSelectInitWrapperState, +getHostProps as ReactDOMSelectGetHostProps, +postMountWrapper as ReactDOMSelectPostMountWrapper, +restoreControlledState as ReactDOMSelectRestoreControlledState, +postUpdateWrapper as ReactDOMSelectPostUpdateWrapper } from +'./ReactDOMSelect'; import { - initWrapperState as ReactDOMTextareaInitWrapperState, - getHostProps as ReactDOMTextareaGetHostProps, - postMountWrapper as ReactDOMTextareaPostMountWrapper, - updateWrapper as ReactDOMTextareaUpdateWrapper, - restoreControlledState as ReactDOMTextareaRestoreControlledState, -} from './ReactDOMTextarea'; -import {track} from './inputValueTracking'; +initWrapperState as ReactDOMTextareaInitWrapperState, +getHostProps as ReactDOMTextareaGetHostProps, +postMountWrapper as ReactDOMTextareaPostMountWrapper, +updateWrapper as ReactDOMTextareaUpdateWrapper, +restoreControlledState as ReactDOMTextareaRestoreControlledState } from +'./ReactDOMTextarea'; +import { track } from './inputValueTracking'; import setInnerHTML from './setInnerHTML'; import setTextContent from './setTextContent'; import { - TOP_ERROR, - TOP_INVALID, - TOP_LOAD, - TOP_RESET, - TOP_SUBMIT, - TOP_TOGGLE, -} from '../events/DOMTopLevelEventTypes'; -import {listenTo, trapBubbledEvent} from '../events/ReactBrowserEventEmitter'; -import {mediaEventTypes} from '../events/DOMTopLevelEventTypes'; +TOP_ERROR, +TOP_INVALID, +TOP_LOAD, +TOP_RESET, +TOP_SUBMIT, +TOP_TOGGLE } from +'../events/DOMTopLevelEventTypes'; +import { listenTo, trapBubbledEvent } from '../events/ReactBrowserEventEmitter'; +import { mediaEventTypes } from '../events/DOMTopLevelEventTypes'; import { - createDangerousStringForStyles, - setValueForStyles, - validateShorthandPropertyCollisionInDev, -} from '../shared/CSSPropertyOperations'; -import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces'; +createDangerousStringForStyles, +setValueForStyles, +validateShorthandPropertyCollisionInDev } from +'../shared/CSSPropertyOperations'; +import { Namespaces, getIntrinsicNamespace } from '../shared/DOMNamespaces'; import { - getPropertyInfo, - shouldIgnoreAttribute, - shouldRemoveAttribute, -} from '../shared/DOMProperty'; +getPropertyInfo, +shouldIgnoreAttribute, +shouldRemoveAttribute } from +'../shared/DOMProperty'; import assertValidProps from '../shared/assertValidProps'; -import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType'; +import { DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE } from '../shared/HTMLNodeType'; import isCustomComponent from '../shared/isCustomComponent'; import possibleStandardNames from '../shared/possibleStandardNames'; -import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook'; -import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook'; -import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook'; +import { validateProperties as validateARIAProperties } from '../shared/ReactDOMInvalidARIAHook'; +import { validateProperties as validateInputProperties } from '../shared/ReactDOMNullInputValuePropHook'; +import { validateProperties as validateUnknownProperties } from '../shared/ReactDOMUnknownPropertyHook'; let didWarnInvalidHydration = false; let didWarnShadyDOM = false; @@ -89,7 +89,7 @@ const CHILDREN = 'children'; const STYLE = 'style'; const HTML = '__html'; -const {html: HTML_NAMESPACE} = Namespaces; +const { html: HTML_NAMESPACE } = Namespaces; let warnedUnknownTags; let suppressHydrationWarning; @@ -118,13 +118,13 @@ if (__DEV__) { // This tag is not present in non Electron environments such as JSDom which // is often used for testing purposes. // @see https://electronjs.org/docs/api/webview-tag - webview: true, - }; + webview: true }; + - validatePropertiesInDevelopment = function(type, props) { + validatePropertiesInDevelopment = function (type, props) { validateARIAProperties(type, props); validateInputProperties(type, props); - validateUnknownProperties(type, props, /* canUseEventSystem */ true); + validateUnknownProperties(type, props, /* canUseEventSystem */true); }; // IE 11 parses & normalizes the style attribute as opposed to other @@ -145,18 +145,18 @@ if (__DEV__) { const NORMALIZE_NEWLINES_REGEX = /\r\n?/g; const NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g; - normalizeMarkupForTextOrAttribute = function(markup: mixed): string { + normalizeMarkupForTextOrAttribute = function (markup: unknown): string { const markupString = - typeof markup === 'string' ? markup : '' + (markup: any); - return markupString - .replace(NORMALIZE_NEWLINES_REGEX, '\n') - .replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, ''); + typeof markup === 'string' ? markup : '' + (markup as any); + return markupString. + replace(NORMALIZE_NEWLINES_REGEX, '\n'). + replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, ''); }; - warnForTextDifference = function( - serverText: string, - clientText: string | number, - ) { + warnForTextDifference = function ( + serverText: string, + clientText: string | number) + { if (didWarnInvalidHydration) { return; } @@ -167,87 +167,87 @@ if (__DEV__) { } didWarnInvalidHydration = true; warningWithoutStack( - false, - 'Text content did not match. Server: "%s" Client: "%s"', - normalizedServerText, - normalizedClientText, - ); + false, + 'Text content did not match. Server: "%s" Client: "%s"', + normalizedServerText, + normalizedClientText); + }; - warnForPropDifference = function( - propName: string, - serverValue: mixed, - clientValue: mixed, - ) { + warnForPropDifference = function ( + propName: string, + serverValue: unknown, + clientValue: unknown) + { if (didWarnInvalidHydration) { return; } const normalizedClientValue = normalizeMarkupForTextOrAttribute( - clientValue, - ); + clientValue); + const normalizedServerValue = normalizeMarkupForTextOrAttribute( - serverValue, - ); + serverValue); + if (normalizedServerValue === normalizedClientValue) { return; } didWarnInvalidHydration = true; warningWithoutStack( - false, - 'Prop `%s` did not match. Server: %s Client: %s', - propName, - JSON.stringify(normalizedServerValue), - JSON.stringify(normalizedClientValue), - ); + false, + 'Prop `%s` did not match. Server: %s Client: %s', + propName, + JSON.stringify(normalizedServerValue), + JSON.stringify(normalizedClientValue)); + }; - warnForExtraAttributes = function(attributeNames: Set) { + warnForExtraAttributes = function (attributeNames: Set) { if (didWarnInvalidHydration) { return; } didWarnInvalidHydration = true; const names = []; - attributeNames.forEach(function(name) { + attributeNames.forEach(function (name) { names.push(name); }); warningWithoutStack(false, 'Extra attributes from the server: %s', names); }; - warnForInvalidEventListener = function(registrationName, listener) { + warnForInvalidEventListener = function (registrationName, listener) { if (listener === false) { warning( - false, - 'Expected `%s` listener to be a function, instead got `false`.\n\n' + - 'If you used to conditionally omit it with %s={condition && value}, ' + - 'pass %s={condition ? value : undefined} instead.', - registrationName, - registrationName, - registrationName, - ); + false, + 'Expected `%s` listener to be a function, instead got `false`.\n\n' + + 'If you used to conditionally omit it with %s={condition && value}, ' + + 'pass %s={condition ? value : undefined} instead.', + registrationName, + registrationName, + registrationName); + } else { warning( - false, - 'Expected `%s` listener to be a function, instead got a value of `%s` type.', - registrationName, - typeof listener, - ); + false, + 'Expected `%s` listener to be a function, instead got a value of `%s` type.', + registrationName, + typeof listener); + } }; // Parse the HTML and read it back to normalize the HTML string so that it // can be used for comparison. - normalizeHTML = function(parent: Element, html: string) { + normalizeHTML = function (parent: Element, html: string) { // We could have created a separate document here to avoid // re-initializing custom elements if they exist. But this breaks // how