mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
* Don't bother including `unstable_` in error The method names don't get stripped out of the production bundles because they are passed as arguments to the error decoder. Let's just always use the unprefixed APIs in the messages. * Set up experimental builds The experimental builds are packaged exactly like builds in the stable release channel: same file structure, entry points, and npm package names. The goal is to match what will eventually be released in stable as closely as possible, but with additional features turned on. Versioning and Releasing ------------------------ The experimental builds will be published to the same registry and package names as the stable ones. However, they will be versioned using a separate scheme. Instead of semver versions, experimental releases will receive arbitrary version strings based on their content hashes. The motivation is to thwart attempts to use a version range to match against future experimental releases. The only way to install or depend on an experimental release is to refer to the specific version number. Building -------- I did not use the existing feature flag infra to configure the experimental builds. The reason is because feature flags are designed to configure a single package. They're not designed to generate multiple forks of the same package; for each set of feature flags, you must create a separate package configuration. Instead, I've added a new build dimension called the **release channel**. By default, builds use the **stable** channel. There's also an **experimental** release channel. We have the option to add more in the future. There are now two dimensions per artifact: build type (production, development, or profiling), and release channel (stable or experimental). These are separate dimensions because they are combinatorial: there are stable and experimental production builds, stable and experimental developmenet builds, and so on. You can add something to an experimental build by gating on `__EXPERIMENTAL__`, similar to how we use `__DEV__`. Anything inside these branches will be excluded from the stable builds. This gives us a low effort way to add experimental behavior in any package without setting up feature flags or configuring a new package.
720 lines
22 KiB
JavaScript
720 lines
22 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 {ReactNodeList} from 'shared/ReactTypes';
|
|
import type {RootTag} from 'shared/ReactRootTags';
|
|
// 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';
|
|
|
|
import '../shared/checkReact';
|
|
import './ReactDOMClientInjection';
|
|
|
|
import {
|
|
findHostInstanceWithNoPortals,
|
|
createContainer,
|
|
updateContainer,
|
|
batchedEventUpdates,
|
|
batchedUpdates,
|
|
unbatchedUpdates,
|
|
discreteUpdates,
|
|
flushDiscreteUpdates,
|
|
flushSync,
|
|
flushControlled,
|
|
injectIntoDevTools,
|
|
getPublicRootInstance,
|
|
findHostInstance,
|
|
findHostInstanceWithWarning,
|
|
flushPassiveEffects,
|
|
IsThisRendererActing,
|
|
attemptSynchronousHydration,
|
|
attemptUserBlockingHydration,
|
|
attemptContinuousHydration,
|
|
attemptHydrationAtCurrentPriority,
|
|
} from 'react-reconciler/inline.dom';
|
|
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
|
|
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
|
import {setBatchingImplementation} from 'legacy-events/ReactGenericBatching';
|
|
import {
|
|
setRestoreImplementation,
|
|
enqueueStateRestore,
|
|
restoreStateIfNeeded,
|
|
} from 'legacy-events/ReactControlledComponent';
|
|
import {injection as EventPluginHubInjection} from 'legacy-events/EventPluginHub';
|
|
import {runEventsInBatch} from 'legacy-events/EventBatching';
|
|
import {eventNameDispatchConfigs} from 'legacy-events/EventPluginRegistry';
|
|
import {
|
|
accumulateTwoPhaseDispatches,
|
|
accumulateDirectDispatches,
|
|
} from 'legacy-events/EventPropagators';
|
|
import {LegacyRoot, ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
|
|
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 lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
|
|
import warningWithoutStack from 'shared/warningWithoutStack';
|
|
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
|
|
|
|
import {
|
|
getInstanceFromNode,
|
|
getNodeFromInstance,
|
|
getFiberCurrentPropsFromNode,
|
|
getClosestInstanceFromNode,
|
|
markContainerAsRoot,
|
|
} from './ReactDOMComponentTree';
|
|
import {restoreControlledState} from './ReactDOMComponent';
|
|
import {dispatchEvent} from '../events/ReactDOMEventListener';
|
|
import {
|
|
setAttemptSynchronousHydration,
|
|
setAttemptUserBlockingHydration,
|
|
setAttemptContinuousHydration,
|
|
setAttemptHydrationAtCurrentPriority,
|
|
eagerlyTrapReplayableEvents,
|
|
queueExplicitHydrationTarget,
|
|
} from '../events/ReactDOMEventReplaying';
|
|
import {
|
|
ELEMENT_NODE,
|
|
COMMENT_NODE,
|
|
DOCUMENT_NODE,
|
|
DOCUMENT_FRAGMENT_NODE,
|
|
} from '../shared/HTMLNodeType';
|
|
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
|
|
|
|
setAttemptSynchronousHydration(attemptSynchronousHydration);
|
|
setAttemptUserBlockingHydration(attemptUserBlockingHydration);
|
|
setAttemptContinuousHydration(attemptContinuousHydration);
|
|
setAttemptHydrationAtCurrentPriority(attemptHydrationAtCurrentPriority);
|
|
|
|
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: ?_ReactRoot,
|
|
_reactHasBeenPassedToCreateRootDEV: ?boolean,
|
|
})
|
|
| (Document & {
|
|
_reactRootContainer: ?_ReactRoot,
|
|
_reactHasBeenPassedToCreateRootDEV: ?boolean,
|
|
});
|
|
|
|
type _ReactRoot = {
|
|
render(children: ReactNodeList, callback: ?() => mixed): void,
|
|
unmount(callback: ?() => mixed): void,
|
|
|
|
_internalRoot: FiberRoot,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
function ReactSyncRoot(
|
|
container: DOMContainer,
|
|
tag: RootTag,
|
|
options: void | RootOptions,
|
|
) {
|
|
this._internalRoot = createRootImpl(container, tag, options);
|
|
}
|
|
|
|
function ReactRoot(container: DOMContainer, options: void | RootOptions) {
|
|
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
|
|
}
|
|
|
|
ReactRoot.prototype.render = ReactSyncRoot.prototype.render = function(
|
|
children: ReactNodeList,
|
|
callback: ?() => mixed,
|
|
): void {
|
|
const root = this._internalRoot;
|
|
callback = callback === undefined ? null : callback;
|
|
if (__DEV__) {
|
|
warnOnInvalidCallback(callback, 'render');
|
|
}
|
|
updateContainer(children, root, null, callback);
|
|
};
|
|
|
|
ReactRoot.prototype.unmount = ReactSyncRoot.prototype.unmount = function(
|
|
callback: ?() => mixed,
|
|
): void {
|
|
const root = this._internalRoot;
|
|
callback = callback === undefined ? null : callback;
|
|
if (__DEV__) {
|
|
warnOnInvalidCallback(callback, 'render');
|
|
}
|
|
updateContainer(null, root, null, callback);
|
|
};
|
|
|
|
/**
|
|
* 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,
|
|
discreteUpdates,
|
|
flushDiscreteUpdates,
|
|
batchedEventUpdates,
|
|
);
|
|
|
|
let warnedAboutHydrateAPI = false;
|
|
|
|
function legacyCreateRootFromDOMContainer(
|
|
container: DOMContainer,
|
|
forceHydrate: boolean,
|
|
): _ReactRoot {
|
|
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;
|
|
lowPriorityWarningWithoutStack(
|
|
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 batched.
|
|
return new ReactSyncRoot(
|
|
container,
|
|
LegacyRoot,
|
|
shouldHydrate
|
|
? {
|
|
hydrate: true,
|
|
}
|
|
: undefined,
|
|
);
|
|
}
|
|
|
|
function legacyRenderSubtreeIntoContainer(
|
|
parentComponent: ?React$Component<any, any>,
|
|
children: ReactNodeList,
|
|
container: DOMContainer,
|
|
forceHydrate: boolean,
|
|
callback: ?Function,
|
|
) {
|
|
if (__DEV__) {
|
|
topLevelUpdateWarnings(container);
|
|
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
|
|
}
|
|
|
|
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
|
|
// member of intersection type." Whyyyyyy.
|
|
let root: _ReactRoot = (container._reactRootContainer: any);
|
|
let fiberRoot;
|
|
if (!root) {
|
|
// Initial mount
|
|
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
|
|
container,
|
|
forceHydrate,
|
|
);
|
|
fiberRoot = root._internalRoot;
|
|
if (typeof callback === 'function') {
|
|
const originalCallback = callback;
|
|
callback = function() {
|
|
const instance = getPublicRootInstance(fiberRoot);
|
|
originalCallback.call(instance);
|
|
};
|
|
}
|
|
// Initial mount should not be batched.
|
|
unbatchedUpdates(() => {
|
|
updateContainer(children, fiberRoot, parentComponent, callback);
|
|
});
|
|
} else {
|
|
fiberRoot = root._internalRoot;
|
|
if (typeof callback === 'function') {
|
|
const originalCallback = callback;
|
|
callback = function() {
|
|
const instance = getPublicRootInstance(fiberRoot);
|
|
originalCallback.call(instance);
|
|
};
|
|
}
|
|
// Update
|
|
updateContainer(children, fiberRoot, parentComponent, callback);
|
|
}
|
|
return getPublicRootInstance(fiberRoot);
|
|
}
|
|
|
|
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<any, any>,
|
|
): 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.createRoot(). This is not supported. ' +
|
|
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
|
|
);
|
|
}
|
|
// TODO: throw or warn if we couldn't hydrate?
|
|
return legacyRenderSubtreeIntoContainer(
|
|
null,
|
|
element,
|
|
container,
|
|
true,
|
|
callback,
|
|
);
|
|
},
|
|
|
|
render(
|
|
element: React$Element<any>,
|
|
container: DOMContainer,
|
|
callback: ?Function,
|
|
) {
|
|
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.createRoot(). This is not supported. ' +
|
|
'Did you mean to call root.render(element)?',
|
|
);
|
|
}
|
|
return legacyRenderSubtreeIntoContainer(
|
|
null,
|
|
element,
|
|
container,
|
|
false,
|
|
callback,
|
|
);
|
|
},
|
|
|
|
unstable_renderSubtreeIntoContainer(
|
|
parentComponent: React$Component<any, any>,
|
|
element: React$Element<any>,
|
|
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.createRoot(). This is not supported. Did you mean to call root.unmount()?',
|
|
);
|
|
}
|
|
|
|
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;
|
|
lowPriorityWarningWithoutStack(
|
|
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,
|
|
|
|
// TODO remove this legacy method, unstable_discreteUpdates replaces it
|
|
unstable_interactiveUpdates: (fn, a, b, c) => {
|
|
flushDiscreteUpdates();
|
|
return discreteUpdates(fn, a, b, c);
|
|
},
|
|
|
|
unstable_discreteUpdates: discreteUpdates,
|
|
unstable_flushDiscreteUpdates: flushDiscreteUpdates,
|
|
|
|
flushSync: flushSync,
|
|
|
|
unstable_createRoot: createRoot,
|
|
unstable_createSyncRoot: createSyncRoot,
|
|
unstable_flushControlled: flushControlled,
|
|
|
|
unstable_scheduleHydration(target: Node) {
|
|
if (target) {
|
|
queueExplicitHydrationTarget(target);
|
|
}
|
|
},
|
|
|
|
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
|
// Keep in sync with ReactDOMUnstableNativeDependencies.js
|
|
// ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification.
|
|
Events: [
|
|
getInstanceFromNode,
|
|
getNodeFromInstance,
|
|
getFiberCurrentPropsFromNode,
|
|
EventPluginHubInjection.injectEventPluginsByName,
|
|
eventNameDispatchConfigs,
|
|
accumulateTwoPhaseDispatches,
|
|
accumulateDirectDispatches,
|
|
enqueueStateRestore,
|
|
restoreStateIfNeeded,
|
|
dispatchEvent,
|
|
runEventsInBatch,
|
|
flushPassiveEffects,
|
|
IsThisRendererActing,
|
|
],
|
|
},
|
|
};
|
|
|
|
type RootOptions = {
|
|
hydrate?: boolean,
|
|
hydrationOptions?: {
|
|
onHydrated?: (suspenseNode: Comment) => void,
|
|
onDeleted?: (suspenseNode: Comment) => void,
|
|
},
|
|
};
|
|
|
|
function createRoot(
|
|
container: DOMContainer,
|
|
options?: RootOptions,
|
|
): _ReactRoot {
|
|
invariant(
|
|
isValidContainer(container),
|
|
'createRoot(...): Target container is not a DOM element.',
|
|
);
|
|
warnIfReactDOMContainerInDEV(container);
|
|
return new ReactRoot(container, options);
|
|
}
|
|
|
|
function createSyncRoot(
|
|
container: DOMContainer,
|
|
options?: RootOptions,
|
|
): _ReactRoot {
|
|
invariant(
|
|
isValidContainer(container),
|
|
'createRoot(...): Target container is not a DOM element.',
|
|
);
|
|
warnIfReactDOMContainerInDEV(container);
|
|
return new ReactSyncRoot(container, BatchedRoot, options);
|
|
}
|
|
|
|
function warnIfReactDOMContainerInDEV(container) {
|
|
if (__DEV__) {
|
|
warningWithoutStack(
|
|
!container._reactRootContainer,
|
|
'You are calling ReactDOM.createRoot() on a container that was previously ' +
|
|
'passed to ReactDOM.render(). This is not supported.',
|
|
);
|
|
container._reactHasBeenPassedToCreateRootDEV = true;
|
|
}
|
|
}
|
|
|
|
if (enableStableConcurrentModeAPIs) {
|
|
ReactDOM.createRoot = createRoot;
|
|
ReactDOM.createSyncRoot = createSyncRoot;
|
|
}
|
|
|
|
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;
|