@samitouri / QOS-React-2 / commits / b8f8258775

Split ReactDOM entry point (#17331)

* Split ReactDOM entry point * BatchedRoot -> BlockingRoot

Dan Abramov committed Nov 10, 2019 at 13:43 UTC b8f8258775211d2d3b1b144cc6c75148ecd4b8d8
6 files changed +610 -537
packages/react-dom/src/client/ReactDOM.js
+21 -532
@@ -7,30 +7,28 @@
7 * @flow
8 */
9
10 +import type {RootType} from './ReactDOMRoot';
11 import type {ReactNodeList} from 'shared/ReactTypes';
11 -import type {RootTag} from 'shared/ReactRootTags';
12 -// TODO: This type is shared between the reconciler and ReactDOM, but will
13 -// eventually be lifted out to the renderer.
14 -import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
12
13 import '../shared/checkReact';
14 import './ReactDOMClientInjection';
15 +import {
16 + findDOMNode,
17 + render,
18 + hydrate,
19 + unstable_renderSubtreeIntoContainer,
20 + unmountComponentAtNode,
21 +} from './ReactDOMLegacy';
22 +import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';
23
24 import {
20 - findHostInstanceWithNoPortals,
21 - createContainer,
22 - updateContainer,
25 batchedEventUpdates,
26 batchedUpdates,
25 - unbatchedUpdates,
27 discreteUpdates,
28 flushDiscreteUpdates,
29 flushSync,
30 flushControlled,
31 injectIntoDevTools,
31 - getPublicRootInstance,
32 - findHostInstance,
33 - findHostInstanceWithWarning,
32 flushPassiveEffects,
33 IsThisRendererActing,
34 attemptSynchronousHydration,
@@ -53,11 +51,7 @@ import {
51 accumulateTwoPhaseDispatches,
52 accumulateDirectDispatches,
53 } from 'legacy-events/EventPropagators';
56 -import {LegacyRoot, ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
57 -import {has as hasInstance} from 'shared/ReactInstanceMap';
54 import ReactVersion from 'shared/ReactVersion';
59 -import ReactSharedInternals from 'shared/ReactSharedInternals';
60 -import getComponentName from 'shared/getComponentName';
55 import invariant from 'shared/invariant';
56 import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
57 import warningWithoutStack from 'shared/warningWithoutStack';
@@ -68,9 +62,6 @@ import {
62 getNodeFromInstance,
63 getFiberCurrentPropsFromNode,
64 getClosestInstanceFromNode,
71 - isContainerMarkedAsRoot,
72 - markContainerAsRoot,
73 - unmarkContainerAsRoot,
65 } from './ReactDOMComponentTree';
66 import {restoreControlledState} from './ReactDOMComponent';
67 import {dispatchEvent} from '../events/ReactDOMEventListener';
@@ -79,26 +70,14 @@ import {
70 setAttemptUserBlockingHydration,
71 setAttemptContinuousHydration,
72 setAttemptHydrationAtCurrentPriority,
82 - eagerlyTrapReplayableEvents,
73 queueExplicitHydrationTarget,
74 } from '../events/ReactDOMEventReplaying';
85 -import {
86 - ELEMENT_NODE,
87 - COMMENT_NODE,
88 - DOCUMENT_NODE,
89 - DOCUMENT_FRAGMENT_NODE,
90 -} from '../shared/HTMLNodeType';
91 -import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
75
76 setAttemptSynchronousHydration(attemptSynchronousHydration);
77 setAttemptUserBlockingHydration(attemptUserBlockingHydration);
78 setAttemptContinuousHydration(attemptContinuousHydration);
79 setAttemptHydrationAtCurrentPriority(attemptHydrationAtCurrentPriority);
80
98 -const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
99 -
100 -let topLevelUpdateWarnings;
101 -let warnOnInvalidCallback;
81 let didWarnAboutUnstableCreatePortal = false;
82
83 if (__DEV__) {
@@ -119,176 +98,9 @@ if (__DEV__) {
98 'polyfill in older browsers. https://fb.me/react-polyfills',
99 );
100 }
122 -
123 - topLevelUpdateWarnings = (container: DOMContainer) => {
124 - if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
125 - const hostInstance = findHostInstanceWithNoPortals(
126 - container._reactRootContainer._internalRoot.current,
127 - );
128 - if (hostInstance) {
129 - warningWithoutStack(
130 - hostInstance.parentNode === container,
131 - 'render(...): It looks like the React-rendered content of this ' +
132 - 'container was removed without using React. This is not ' +
133 - 'supported and will cause errors. Instead, call ' +
134 - 'ReactDOM.unmountComponentAtNode to empty a container.',
135 - );
136 - }
137 - }
138 -
139 - const isRootRenderedBySomeReact = !!container._reactRootContainer;
140 - const rootEl = getReactRootElementInContainer(container);
141 - const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
142 -
143 - warningWithoutStack(
144 - !hasNonRootReactChild || isRootRenderedBySomeReact,
145 - 'render(...): Replacing React-rendered children with a new root ' +
146 - 'component. If you intended to update the children of this node, ' +
147 - 'you should instead have the existing children update their state ' +
148 - 'and render the new components instead of calling ReactDOM.render.',
149 - );
150 -
151 - warningWithoutStack(
152 - container.nodeType !== ELEMENT_NODE ||
153 - !((container: any): Element).tagName ||
154 - ((container: any): Element).tagName.toUpperCase() !== 'BODY',
155 - 'render(): Rendering components directly into document.body is ' +
156 - 'discouraged, since its children are often manipulated by third-party ' +
157 - 'scripts and browser extensions. This may lead to subtle ' +
158 - 'reconciliation issues. Try rendering into a container element created ' +
159 - 'for your app.',
160 - );
161 - };
162 -
163 - warnOnInvalidCallback = function(callback: mixed, callerName: string) {
164 - warningWithoutStack(
165 - callback === null || typeof callback === 'function',
166 - '%s(...): Expected the last optional `callback` argument to be a ' +
167 - 'function. Instead received: %s.',
168 - callerName,
169 - callback,
170 - );
171 - };
101 }
102
103 setRestoreImplementation(restoreControlledState);
175 -
176 -export type DOMContainer =
177 - | (Element & {
178 - _reactRootContainer: ?_ReactRoot,
179 - })
180 - | (Document & {
181 - _reactRootContainer: ?_ReactRoot,
182 - });
183 -
184 -type _ReactRoot = {
185 - render(children: ReactNodeList, callback: ?() => mixed): void,
186 - unmount(callback: ?() => mixed): void,
187 -
188 - _internalRoot: FiberRoot,
189 -};
190 -
191 -function createRootImpl(
192 - container: DOMContainer,
193 - tag: RootTag,
194 - options: void | RootOptions,
195 -) {
196 - // Tag is either LegacyRoot or Concurrent Root
197 - const hydrate = options != null && options.hydrate === true;
198 - const hydrationCallbacks =
199 - (options != null && options.hydrationOptions) || null;
200 - const root = createContainer(container, tag, hydrate, hydrationCallbacks);
201 - markContainerAsRoot(root.current, container);
202 - if (hydrate && tag !== LegacyRoot) {
203 - const doc =
204 - container.nodeType === DOCUMENT_NODE
205 - ? container
206 - : container.ownerDocument;
207 - eagerlyTrapReplayableEvents(doc);
208 - }
209 - return root;
210 -}
211 -
212 -function ReactBlockingRoot(
213 - container: DOMContainer,
214 - tag: RootTag,
215 - options: void | RootOptions,
216 -) {
217 - this._internalRoot = createRootImpl(container, tag, options);
218 -}
219 -
220 -function ReactRoot(container: DOMContainer, options: void | RootOptions) {
221 - this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
222 -}
223 -
224 -ReactRoot.prototype.render = ReactBlockingRoot.prototype.render = function(
225 - children: ReactNodeList,
226 - callback: ?() => mixed,
227 -): void {
228 - const root = this._internalRoot;
229 - const cb = callback === undefined ? null : callback;
230 - if (__DEV__) {
231 - warnOnInvalidCallback(cb, 'render');
232 - }
233 - updateContainer(children, root, null, cb);
234 -};
235 -
236 -ReactRoot.prototype.unmount = ReactBlockingRoot.prototype.unmount = function(
237 - callback: ?() => mixed,
238 -): void {
239 - const root = this._internalRoot;
240 - const cb = callback === undefined ? null : callback;
241 - if (__DEV__) {
242 - warnOnInvalidCallback(cb, 'render');
243 - }
244 - const container = root.containerInfo;
245 - updateContainer(null, root, null, () => {
246 - unmarkContainerAsRoot(container);
247 - if (cb !== null) {
248 - cb();
249 - }
250 - });
251 -};
252 -
253 -/**
254 - * True if the supplied DOM node is a valid node element.
255 - *
256 - * @param {?DOMElement} node The candidate DOM node.
257 - * @return {boolean} True if the DOM is a valid DOM node.
258 - * @internal
259 - */
260 -function isValidContainer(node) {
261 - return !!(
262 - node &&
263 - (node.nodeType === ELEMENT_NODE ||
264 - node.nodeType === DOCUMENT_NODE ||
265 - node.nodeType === DOCUMENT_FRAGMENT_NODE ||
266 - (node.nodeType === COMMENT_NODE &&
267 - node.nodeValue === ' react-mount-point-unstable '))
268 - );
269 -}
270 -
271 -function getReactRootElementInContainer(container: any) {
272 - if (!container) {
273 - return null;
274 - }
275 -
276 - if (container.nodeType === DOCUMENT_NODE) {
277 - return container.documentElement;
278 - } else {
279 - return container.firstChild;
280 - }
281 -}
282 -
283 -function shouldHydrateDueToLegacyHeuristic(container) {
284 - const rootElement = getReactRootElementInContainer(container);
285 - return !!(
286 - rootElement &&
287 - rootElement.nodeType === ELEMENT_NODE &&
288 - rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
289 - );
290 -}
291 -
104 setBatchingImplementation(
105 batchedUpdates,
106 discreteUpdates,
@@ -296,109 +108,13 @@ setBatchingImplementation(
108 batchedEventUpdates,
109 );
110
299 -let warnedAboutHydrateAPI = false;
300 -
301 -function legacyCreateRootFromDOMContainer(
302 - container: DOMContainer,
303 - forceHydrate: boolean,
304 -): _ReactRoot {
305 - const shouldHydrate =
306 - forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
307 - // First clear any existing content.
308 - if (!shouldHydrate) {
309 - let warned = false;
310 - let rootSibling;
311 - while ((rootSibling = container.lastChild)) {
312 - if (__DEV__) {
313 - if (
314 - !warned &&
315 - rootSibling.nodeType === ELEMENT_NODE &&
316 - (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
317 - ) {
318 - warned = true;
319 - warningWithoutStack(
320 - false,
321 - 'render(): Target node has markup rendered by React, but there ' +
322 - 'are unrelated nodes as well. This is most commonly caused by ' +
323 - 'white-space inserted around server-rendered markup.',
324 - );
325 - }
326 - }
327 - container.removeChild(rootSibling);
328 - }
329 - }
330 - if (__DEV__) {
331 - if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
332 - warnedAboutHydrateAPI = true;
333 - lowPriorityWarningWithoutStack(
334 - false,
335 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
336 - 'will stop working in React v17. Replace the ReactDOM.render() call ' +
337 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
338 - );
339 - }
340 - }
341 -
342 - // Legacy roots are not batched.
343 - return new ReactBlockingRoot(
344 - container,
345 - LegacyRoot,
346 - shouldHydrate
347 - ? {
348 - hydrate: true,
349 - }
350 - : undefined,
351 - );
352 -}
353 -
354 -function legacyRenderSubtreeIntoContainer(
355 - parentComponent: ?React$Component<any, any>,
356 - children: ReactNodeList,
357 - container: DOMContainer,
358 - forceHydrate: boolean,
359 - callback: ?Function,
360 -) {
361 - if (__DEV__) {
362 - topLevelUpdateWarnings(container);
363 - warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
364 - }
365 -
366 - // TODO: Without `any` type, Flow says "Property cannot be accessed on any
367 - // member of intersection type." Whyyyyyy.
368 - let root: _ReactRoot = (container._reactRootContainer: any);
369 - let fiberRoot;
370 - if (!root) {
371 - // Initial mount
372 - root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
373 - container,
374 - forceHydrate,
375 - );
376 - fiberRoot = root._internalRoot;
377 - if (typeof callback === 'function') {
378 - const originalCallback = callback;
379 - callback = function() {
380 - const instance = getPublicRootInstance(fiberRoot);
381 - originalCallback.call(instance);
382 - };
383 - }
384 - // Initial mount should not be batched.
385 - unbatchedUpdates(() => {
386 - updateContainer(children, fiberRoot, parentComponent, callback);
111 +export type DOMContainer =
112 + | (Element & {
113 + _reactRootContainer: ?RootType,
114 + })
115 + | (Document & {
116 + _reactRootContainer: ?RootType,
117 });
388 - } else {
389 - fiberRoot = root._internalRoot;
390 - if (typeof callback === 'function') {
391 - const originalCallback = callback;
392 - callback = function() {
393 - const instance = getPublicRootInstance(fiberRoot);
394 - originalCallback.call(instance);
395 - };
396 - }
397 - // Update
398 - updateContainer(children, fiberRoot, parentComponent, callback);
399 - }
400 - return getPublicRootInstance(fiberRoot);
401 -}
118
119 function createPortal(
120 children: ReactNodeList,
@@ -416,186 +132,12 @@ function createPortal(
132 const ReactDOM: Object = {
133 createPortal,
134
419 - findDOMNode(
420 - componentOrElement: Element | ?React$Component<any, any>,
421 - ): null | Element | Text {
422 - if (__DEV__) {
423 - let owner = (ReactCurrentOwner.current: any);
424 - if (owner !== null && owner.stateNode !== null) {
425 - const warnedAboutRefsInRender =
426 - owner.stateNode._warnedAboutRefsInRender;
427 - warningWithoutStack(
428 - warnedAboutRefsInRender,
429 - '%s is accessing findDOMNode inside its render(). ' +
430 - 'render() should be a pure function of props and state. It should ' +
431 - 'never access something that requires stale data from the previous ' +
432 - 'render, such as refs. Move this logic to componentDidMount and ' +
433 - 'componentDidUpdate instead.',
434 - getComponentName(owner.type) || 'A component',
435 - );
436 - owner.stateNode._warnedAboutRefsInRender = true;
437 - }
438 - }
439 - if (componentOrElement == null) {
440 - return null;
441 - }
442 - if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
443 - return (componentOrElement: any);
444 - }
445 - if (__DEV__) {
446 - return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
447 - }
448 - return findHostInstance(componentOrElement);
449 - },
450 -
451 - hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
452 - invariant(
453 - isValidContainer(container),
454 - 'Target container is not a DOM element.',
455 - );
456 - if (__DEV__) {
457 - const isModernRoot =
458 - isContainerMarkedAsRoot(container) &&
459 - container._reactRootContainer === undefined;
460 - if (isModernRoot) {
461 - warningWithoutStack(
462 - false,
463 - 'You are calling ReactDOM.hydrate() on a container that was previously ' +
464 - 'passed to ReactDOM.createRoot(). This is not supported. ' +
465 - 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
466 - );
467 - }
468 - }
469 - // TODO: throw or warn if we couldn't hydrate?
470 - return legacyRenderSubtreeIntoContainer(
471 - null,
472 - element,
473 - container,
474 - true,
475 - callback,
476 - );
477 - },
478 -
479 - render(
480 - element: React$Element<any>,
481 - container: DOMContainer,
482 - callback: ?Function,
483 - ) {
484 - invariant(
485 - isValidContainer(container),
486 - 'Target container is not a DOM element.',
487 - );
488 - if (__DEV__) {
489 - const isModernRoot =
490 - isContainerMarkedAsRoot(container) &&
491 - container._reactRootContainer === undefined;
492 - if (isModernRoot) {
493 - warningWithoutStack(
494 - false,
495 - 'You are calling ReactDOM.render() on a container that was previously ' +
496 - 'passed to ReactDOM.createRoot(). This is not supported. ' +
497 - 'Did you mean to call root.render(element)?',
498 - );
499 - }
500 - }
501 - return legacyRenderSubtreeIntoContainer(
502 - null,
503 - element,
504 - container,
505 - false,
506 - callback,
507 - );
508 - },
509 -
510 - unstable_renderSubtreeIntoContainer(
511 - parentComponent: React$Component<any, any>,
512 - element: React$Element<any>,
513 - containerNode: DOMContainer,
514 - callback: ?Function,
515 - ) {
516 - invariant(
517 - isValidContainer(containerNode),
518 - 'Target container is not a DOM element.',
519 - );
520 - invariant(
521 - parentComponent != null && hasInstance(parentComponent),
522 - 'parentComponent must be a valid React Component',
523 - );
524 - return legacyRenderSubtreeIntoContainer(
525 - parentComponent,
526 - element,
527 - containerNode,
528 - false,
529 - callback,
530 - );
531 - },
532 -
533 - unmountComponentAtNode(container: DOMContainer) {
534 - invariant(
535 - isValidContainer(container),
536 - 'unmountComponentAtNode(...): Target container is not a DOM element.',
537 - );
538 -
539 - if (__DEV__) {
540 - const isModernRoot =
541 - isContainerMarkedAsRoot(container) &&
542 - container._reactRootContainer === undefined;
543 - if (isModernRoot) {
544 - warningWithoutStack(
545 - false,
546 - 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
547 - 'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',
548 - );
549 - }
550 - }
551 -
552 - if (container._reactRootContainer) {
553 - if (__DEV__) {
554 - const rootEl = getReactRootElementInContainer(container);
555 - const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
556 - warningWithoutStack(
557 - !renderedByDifferentReact,
558 - "unmountComponentAtNode(): The node you're attempting to unmount " +
559 - 'was rendered by another copy of React.',
560 - );
561 - }
562 -
563 - // Unmount should not be batched.
564 - unbatchedUpdates(() => {
565 - legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
566 - container._reactRootContainer = null;
567 - unmarkContainerAsRoot(container);
568 - });
569 - });
570 - // If you call unmountComponentAtNode twice in quick succession, you'll
571 - // get `true` twice. That's probably fine?
572 - return true;
573 - } else {
574 - if (__DEV__) {
575 - const rootEl = getReactRootElementInContainer(container);
576 - const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
577 -
578 - // Check if the container itself is a React root node.
579 - const isContainerReactRoot =
580 - container.nodeType === ELEMENT_NODE &&
581 - isValidContainer(container.parentNode) &&
582 - !!container.parentNode._reactRootContainer;
583 -
584 - warningWithoutStack(
585 - !hasNonRootReactChild,
586 - "unmountComponentAtNode(): The node you're attempting to unmount " +
587 - 'was rendered by React and is not a top-level container. %s',
588 - isContainerReactRoot
589 - ? 'You may have accidentally passed in a React root node instead ' +
590 - 'of its container.'
591 - : 'Instead, have the parent component update its state and ' +
592 - 'rerender in order to remove this component.',
593 - );
594 - }
595 -
596 - return false;
597 - }
598 - },
135 + // Legacy
136 + findDOMNode,
137 + hydrate,
138 + render,
139 + unstable_renderSubtreeIntoContainer,
140 + unmountComponentAtNode,
141
142 // Temporary alias since we already shipped React 16 RC with it.
143 // TODO: remove in React 17.
@@ -638,59 +180,6 @@ const ReactDOM: Object = {
180 },
181 };
182
641 -type RootOptions = {
642 - hydrate?: boolean,
643 - hydrationOptions?: {
644 - onHydrated?: (suspenseNode: Comment) => void,
645 - onDeleted?: (suspenseNode: Comment) => void,
646 - },
647 -};
648 -
649 -function createRoot(
650 - container: DOMContainer,
651 - options?: RootOptions,
652 -): _ReactRoot {
653 - invariant(
654 - isValidContainer(container),
655 - 'createRoot(...): Target container is not a DOM element.',
656 - );
657 - warnIfReactDOMContainerInDEV(container);
658 - return new ReactRoot(container, options);
659 -}
660 -
661 -function createBlockingRoot(
662 - container: DOMContainer,
663 - options?: RootOptions,
664 -): _ReactRoot {
665 - invariant(
666 - isValidContainer(container),
667 - 'createRoot(...): Target container is not a DOM element.',
668 - );
669 - warnIfReactDOMContainerInDEV(container);
670 - return new ReactBlockingRoot(container, BatchedRoot, options);
671 -}
672 -
673 -function warnIfReactDOMContainerInDEV(container) {
674 - if (__DEV__) {
675 - if (isContainerMarkedAsRoot(container)) {
676 - if (container._reactRootContainer) {
677 - warningWithoutStack(
678 - false,
679 - 'You are calling ReactDOM.createRoot() on a container that was previously ' +
680 - 'passed to ReactDOM.render(). This is not supported.',
681 - );
682 - } else {
683 - warningWithoutStack(
684 - false,
685 - 'You are calling ReactDOM.createRoot() on a container that ' +
686 - 'has already been passed to createRoot() before. Instead, call ' +
687 - 'root.render() on the existing root instead if you want to update it.',
688 - );
689 - }
690 - }
691 - }
692 -}
693 -
183 if (exposeConcurrentModeAPIs) {
184 ReactDOM.createRoot = createRoot;
185 ReactDOM.createBlockingRoot = createBlockingRoot;
packages/react-dom/src/client/ReactDOMLegacy.js new
+396
@@ -0,0 +1,396 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {DOMContainer} from './ReactDOM';
11 +import type {RootType} from './ReactDOMRoot';
12 +import type {ReactNodeList} from 'shared/ReactTypes';
13 +
14 +import {
15 + getInstanceFromNode,
16 + isContainerMarkedAsRoot,
17 + unmarkContainerAsRoot,
18 +} from './ReactDOMComponentTree';
19 +import {
20 + createLegacyRoot,
21 + isValidContainer,
22 + warnOnInvalidCallback,
23 +} from './ReactDOMRoot';
24 +import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
25 +import {
26 + DOCUMENT_NODE,
27 + ELEMENT_NODE,
28 + COMMENT_NODE,
29 +} from '../shared/HTMLNodeType';
30 +
31 +import {
32 + findHostInstanceWithNoPortals,
33 + updateContainer,
34 + unbatchedUpdates,
35 + getPublicRootInstance,
36 + findHostInstance,
37 + findHostInstanceWithWarning,
38 +} from 'react-reconciler/inline.dom';
39 +import getComponentName from 'shared/getComponentName';
40 +import invariant from 'shared/invariant';
41 +import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
42 +import warningWithoutStack from 'shared/warningWithoutStack';
43 +import ReactSharedInternals from 'shared/ReactSharedInternals';
44 +import {has as hasInstance} from 'shared/ReactInstanceMap';
45 +
46 +const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
47 +
48 +let topLevelUpdateWarnings;
49 +let warnedAboutHydrateAPI = false;
50 +
51 +if (__DEV__) {
52 + topLevelUpdateWarnings = (container: DOMContainer) => {
53 + if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
54 + const hostInstance = findHostInstanceWithNoPortals(
55 + container._reactRootContainer._internalRoot.current,
56 + );
57 + if (hostInstance) {
58 + warningWithoutStack(
59 + hostInstance.parentNode === container,
60 + 'render(...): It looks like the React-rendered content of this ' +
61 + 'container was removed without using React. This is not ' +
62 + 'supported and will cause errors. Instead, call ' +
63 + 'ReactDOM.unmountComponentAtNode to empty a container.',
64 + );
65 + }
66 + }
67 +
68 + const isRootRenderedBySomeReact = !!container._reactRootContainer;
69 + const rootEl = getReactRootElementInContainer(container);
70 + const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
71 +
72 + warningWithoutStack(
73 + !hasNonRootReactChild || isRootRenderedBySomeReact,
74 + 'render(...): Replacing React-rendered children with a new root ' +
75 + 'component. If you intended to update the children of this node, ' +
76 + 'you should instead have the existing children update their state ' +
77 + 'and render the new components instead of calling ReactDOM.render.',
78 + );
79 +
80 + warningWithoutStack(
81 + container.nodeType !== ELEMENT_NODE ||
82 + !((container: any): Element).tagName ||
83 + ((container: any): Element).tagName.toUpperCase() !== 'BODY',
84 + 'render(): Rendering components directly into document.body is ' +
85 + 'discouraged, since its children are often manipulated by third-party ' +
86 + 'scripts and browser extensions. This may lead to subtle ' +
87 + 'reconciliation issues. Try rendering into a container element created ' +
88 + 'for your app.',
89 + );
90 + };
91 +}
92 +
93 +function getReactRootElementInContainer(container: any) {
94 + if (!container) {
95 + return null;
96 + }
97 +
98 + if (container.nodeType === DOCUMENT_NODE) {
99 + return container.documentElement;
100 + } else {
101 + return container.firstChild;
102 + }
103 +}
104 +
105 +function shouldHydrateDueToLegacyHeuristic(container) {
106 + const rootElement = getReactRootElementInContainer(container);
107 + return !!(
108 + rootElement &&
109 + rootElement.nodeType === ELEMENT_NODE &&
110 + rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
111 + );
112 +}
113 +
114 +function legacyCreateRootFromDOMContainer(
115 + container: DOMContainer,
116 + forceHydrate: boolean,
117 +): RootType {
118 + const shouldHydrate =
119 + forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
120 + // First clear any existing content.
121 + if (!shouldHydrate) {
122 + let warned = false;
123 + let rootSibling;
124 + while ((rootSibling = container.lastChild)) {
125 + if (__DEV__) {
126 + if (
127 + !warned &&
128 + rootSibling.nodeType === ELEMENT_NODE &&
129 + (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
130 + ) {
131 + warned = true;
132 + warningWithoutStack(
133 + false,
134 + 'render(): Target node has markup rendered by React, but there ' +
135 + 'are unrelated nodes as well. This is most commonly caused by ' +
136 + 'white-space inserted around server-rendered markup.',
137 + );
138 + }
139 + }
140 + container.removeChild(rootSibling);
141 + }
142 + }
143 + if (__DEV__) {
144 + if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
145 + warnedAboutHydrateAPI = true;
146 + lowPriorityWarningWithoutStack(
147 + false,
148 + 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
149 + 'will stop working in React v17. Replace the ReactDOM.render() call ' +
150 + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
151 + );
152 + }
153 + }
154 +
155 + return createLegacyRoot(
156 + container,
157 + shouldHydrate
158 + ? {
159 + hydrate: true,
160 + }
161 + : undefined,
162 + );
163 +}
164 +
165 +function legacyRenderSubtreeIntoContainer(
166 + parentComponent: ?React$Component<any, any>,
167 + children: ReactNodeList,
168 + container: DOMContainer,
169 + forceHydrate: boolean,
170 + callback: ?Function,
171 +) {
172 + if (__DEV__) {
173 + topLevelUpdateWarnings(container);
174 + warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
175 + }
176 +
177 + // TODO: Without `any` type, Flow says "Property cannot be accessed on any
178 + // member of intersection type." Whyyyyyy.
179 + let root: RootType = (container._reactRootContainer: any);
180 + let fiberRoot;
181 + if (!root) {
182 + // Initial mount
183 + root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
184 + container,
185 + forceHydrate,
186 + );
187 + fiberRoot = root._internalRoot;
188 + if (typeof callback === 'function') {
189 + const originalCallback = callback;
190 + callback = function() {
191 + const instance = getPublicRootInstance(fiberRoot);
192 + originalCallback.call(instance);
193 + };
194 + }
195 + // Initial mount should not be batched.
196 + unbatchedUpdates(() => {
197 + updateContainer(children, fiberRoot, parentComponent, callback);
198 + });
199 + } else {
200 + fiberRoot = root._internalRoot;
201 + if (typeof callback === 'function') {
202 + const originalCallback = callback;
203 + callback = function() {
204 + const instance = getPublicRootInstance(fiberRoot);
205 + originalCallback.call(instance);
206 + };
207 + }
208 + // Update
209 + updateContainer(children, fiberRoot, parentComponent, callback);
210 + }
211 + return getPublicRootInstance(fiberRoot);
212 +}
213 +
214 +export function findDOMNode(
215 + componentOrElement: Element | ?React$Component<any, any>,
216 +): null | Element | Text {
217 + if (__DEV__) {
218 + let owner = (ReactCurrentOwner.current: any);
219 + if (owner !== null && owner.stateNode !== null) {
220 + const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
221 + warningWithoutStack(
222 + warnedAboutRefsInRender,
223 + '%s is accessing findDOMNode inside its render(). ' +
224 + 'render() should be a pure function of props and state. It should ' +
225 + 'never access something that requires stale data from the previous ' +
226 + 'render, such as refs. Move this logic to componentDidMount and ' +
227 + 'componentDidUpdate instead.',
228 + getComponentName(owner.type) || 'A component',
229 + );
230 + owner.stateNode._warnedAboutRefsInRender = true;
231 + }
232 + }
233 + if (componentOrElement == null) {
234 + return null;
235 + }
236 + if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
237 + return (componentOrElement: any);
238 + }
239 + if (__DEV__) {
240 + return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
241 + }
242 + return findHostInstance(componentOrElement);
243 +}
244 +
245 +export function hydrate(
246 + element: React$Node,
247 + container: DOMContainer,
248 + callback: ?Function,
249 +) {
250 + invariant(
251 + isValidContainer(container),
252 + 'Target container is not a DOM element.',
253 + );
254 + if (__DEV__) {
255 + const isModernRoot =
256 + isContainerMarkedAsRoot(container) &&
257 + container._reactRootContainer === undefined;
258 + if (isModernRoot) {
259 + warningWithoutStack(
260 + false,
261 + 'You are calling ReactDOM.hydrate() on a container that was previously ' +
262 + 'passed to ReactDOM.createRoot(). This is not supported. ' +
263 + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
264 + );
265 + }
266 + }
267 + // TODO: throw or warn if we couldn't hydrate?
268 + return legacyRenderSubtreeIntoContainer(
269 + null,
270 + element,
271 + container,
272 + true,
273 + callback,
274 + );
275 +}
276 +
277 +export function render(
278 + element: React$Element<any>,
279 + container: DOMContainer,
280 + callback: ?Function,
281 +) {
282 + invariant(
283 + isValidContainer(container),
284 + 'Target container is not a DOM element.',
285 + );
286 + if (__DEV__) {
287 + const isModernRoot =
288 + isContainerMarkedAsRoot(container) &&
289 + container._reactRootContainer === undefined;
290 + if (isModernRoot) {
291 + warningWithoutStack(
292 + false,
293 + 'You are calling ReactDOM.render() on a container that was previously ' +
294 + 'passed to ReactDOM.createRoot(). This is not supported. ' +
295 + 'Did you mean to call root.render(element)?',
296 + );
297 + }
298 + }
299 + return legacyRenderSubtreeIntoContainer(
300 + null,
301 + element,
302 + container,
303 + false,
304 + callback,
305 + );
306 +}
307 +
308 +export function unstable_renderSubtreeIntoContainer(
309 + parentComponent: React$Component<any, any>,
310 + element: React$Element<any>,
311 + containerNode: DOMContainer,
312 + callback: ?Function,
313 +) {
314 + invariant(
315 + isValidContainer(containerNode),
316 + 'Target container is not a DOM element.',
317 + );
318 + invariant(
319 + parentComponent != null && hasInstance(parentComponent),
320 + 'parentComponent must be a valid React Component',
321 + );
322 + return legacyRenderSubtreeIntoContainer(
323 + parentComponent,
324 + element,
325 + containerNode,
326 + false,
327 + callback,
328 + );
329 +}
330 +
331 +export function unmountComponentAtNode(container: DOMContainer) {
332 + invariant(
333 + isValidContainer(container),
334 + 'unmountComponentAtNode(...): Target container is not a DOM element.',
335 + );
336 +
337 + if (__DEV__) {
338 + const isModernRoot =
339 + isContainerMarkedAsRoot(container) &&
340 + container._reactRootContainer === undefined;
341 + if (isModernRoot) {
342 + warningWithoutStack(
343 + false,
344 + 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
345 + 'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',
346 + );
347 + }
348 + }
349 +
350 + if (container._reactRootContainer) {
351 + if (__DEV__) {
352 + const rootEl = getReactRootElementInContainer(container);
353 + const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
354 + warningWithoutStack(
355 + !renderedByDifferentReact,
356 + "unmountComponentAtNode(): The node you're attempting to unmount " +
357 + 'was rendered by another copy of React.',
358 + );
359 + }
360 +
361 + // Unmount should not be batched.
362 + unbatchedUpdates(() => {
363 + legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
364 + container._reactRootContainer = null;
365 + unmarkContainerAsRoot(container);
366 + });
367 + });
368 + // If you call unmountComponentAtNode twice in quick succession, you'll
369 + // get `true` twice. That's probably fine?
370 + return true;
371 + } else {
372 + if (__DEV__) {
373 + const rootEl = getReactRootElementInContainer(container);
374 + const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
375 +
376 + // Check if the container itself is a React root node.
377 + const isContainerReactRoot =
378 + container.nodeType === ELEMENT_NODE &&
379 + isValidContainer(container.parentNode) &&
380 + !!container.parentNode._reactRootContainer;
381 +
382 + warningWithoutStack(
383 + !hasNonRootReactChild,
384 + "unmountComponentAtNode(): The node you're attempting to unmount " +
385 + 'was rendered by React and is not a top-level container. %s',
386 + isContainerReactRoot
387 + ? 'You may have accidentally passed in a React root node instead ' +
388 + 'of its container.'
389 + : 'Instead, have the parent component update its state and ' +
390 + 'rerender in order to remove this component.',
391 + );
392 + }
393 +
394 + return false;
395 + }
396 +}
packages/react-dom/src/client/ReactDOMRoot.js new
+188
@@ -0,0 +1,188 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {DOMContainer} from './ReactDOM';
11 +import type {RootTag} from 'shared/ReactRootTags';
12 +import type {ReactNodeList} from 'shared/ReactTypes';
13 +// TODO: This type is shared between the reconciler and ReactDOM, but will
14 +// eventually be lifted out to the renderer.
15 +import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
16 +
17 +export type RootType = {
18 + render(children: ReactNodeList, callback: ?() => mixed): void,
19 + unmount(callback: ?() => mixed): void,
20 +
21 + _internalRoot: FiberRoot,
22 +};
23 +
24 +export type RootOptions = {
25 + hydrate?: boolean,
26 + hydrationOptions?: {
27 + onHydrated?: (suspenseNode: Comment) => void,
28 + onDeleted?: (suspenseNode: Comment) => void,
29 + },
30 +};
31 +
32 +import {
33 + isContainerMarkedAsRoot,
34 + markContainerAsRoot,
35 + unmarkContainerAsRoot,
36 +} from './ReactDOMComponentTree';
37 +import {eagerlyTrapReplayableEvents} from '../events/ReactDOMEventReplaying';
38 +import {
39 + ELEMENT_NODE,
40 + COMMENT_NODE,
41 + DOCUMENT_NODE,
42 + DOCUMENT_FRAGMENT_NODE,
43 +} from '../shared/HTMLNodeType';
44 +
45 +import {createContainer, updateContainer} from 'react-reconciler/inline.dom';
46 +import invariant from 'shared/invariant';
47 +import warningWithoutStack from 'shared/warningWithoutStack';
48 +import {BlockingRoot, ConcurrentRoot, LegacyRoot} from 'shared/ReactRootTags';
49 +
50 +function ReactDOMRoot(container: DOMContainer, options: void | RootOptions) {
51 + this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
52 +}
53 +
54 +function ReactDOMBlockingRoot(
55 + container: DOMContainer,
56 + tag: RootTag,
57 + options: void | RootOptions,
58 +) {
59 + this._internalRoot = createRootImpl(container, tag, options);
60 +}
61 +
62 +ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
63 + children: ReactNodeList,
64 + callback: ?() => mixed,
65 +): void {
66 + const root = this._internalRoot;
67 + const cb = callback === undefined ? null : callback;
68 + if (__DEV__) {
69 + warnOnInvalidCallback(cb, 'render');
70 + }
71 + updateContainer(children, root, null, cb);
72 +};
73 +
74 +ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(
75 + callback: ?() => mixed,
76 +): void {
77 + const root = this._internalRoot;
78 + const cb = callback === undefined ? null : callback;
79 + if (__DEV__) {
80 + warnOnInvalidCallback(cb, 'render');
81 + }
82 + const container = root.containerInfo;
83 + updateContainer(null, root, null, () => {
84 + unmarkContainerAsRoot(container);
85 + if (cb !== null) {
86 + cb();
87 + }
88 + });
89 +};
90 +
91 +function createRootImpl(
92 + container: DOMContainer,
93 + tag: RootTag,
94 + options: void | RootOptions,
95 +) {
96 + // Tag is either LegacyRoot or Concurrent Root
97 + const hydrate = options != null && options.hydrate === true;
98 + const hydrationCallbacks =
99 + (options != null && options.hydrationOptions) || null;
100 + const root = createContainer(container, tag, hydrate, hydrationCallbacks);
101 + markContainerAsRoot(root.current, container);
102 + if (hydrate && tag !== LegacyRoot) {
103 + const doc =
104 + container.nodeType === DOCUMENT_NODE
105 + ? container
106 + : container.ownerDocument;
107 + eagerlyTrapReplayableEvents(doc);
108 + }
109 + return root;
110 +}
111 +
112 +export function createRoot(
113 + container: DOMContainer,
114 + options?: RootOptions,
115 +): RootType {
116 + invariant(
117 + isValidContainer(container),
118 + 'createRoot(...): Target container is not a DOM element.',
119 + );
120 + warnIfReactDOMContainerInDEV(container);
121 + return new ReactDOMRoot(container, options);
122 +}
123 +
124 +export function createBlockingRoot(
125 + container: DOMContainer,
126 + options?: RootOptions,
127 +): RootType {
128 + invariant(
129 + isValidContainer(container),
130 + 'createRoot(...): Target container is not a DOM element.',
131 + );
132 + warnIfReactDOMContainerInDEV(container);
133 + return new ReactDOMBlockingRoot(container, BlockingRoot, options);
134 +}
135 +
136 +export function createLegacyRoot(
137 + container: DOMContainer,
138 + options?: RootOptions,
139 +): RootType {
140 + return new ReactDOMBlockingRoot(container, LegacyRoot, options);
141 +}
142 +
143 +export function isValidContainer(node: mixed): boolean {
144 + return !!(
145 + node &&
146 + (node.nodeType === ELEMENT_NODE ||
147 + node.nodeType === DOCUMENT_NODE ||
148 + node.nodeType === DOCUMENT_FRAGMENT_NODE ||
149 + (node.nodeType === COMMENT_NODE &&
150 + (node: any).nodeValue === ' react-mount-point-unstable '))
151 + );
152 +}
153 +
154 +export function warnOnInvalidCallback(
155 + callback: mixed,
156 + callerName: string,
157 +): void {
158 + if (__DEV__) {
159 + warningWithoutStack(
160 + callback === null || typeof callback === 'function',
161 + '%s(...): Expected the last optional `callback` argument to be a ' +
162 + 'function. Instead received: %s.',
163 + callerName,
164 + callback,
165 + );
166 + }
167 +}
168 +
169 +function warnIfReactDOMContainerInDEV(container) {
170 + if (__DEV__) {
171 + if (isContainerMarkedAsRoot(container)) {
172 + if (container._reactRootContainer) {
173 + warningWithoutStack(
174 + false,
175 + 'You are calling ReactDOM.createRoot() on a container that was previously ' +
176 + 'passed to ReactDOM.render(). This is not supported.',
177 + );
178 + } else {
179 + warningWithoutStack(
180 + false,
181 + 'You are calling ReactDOM.createRoot() on a container that ' +
182 + 'has already been passed to createRoot() before. Instead, call ' +
183 + 'root.render() on the existing root instead if you want to update it.',
184 + );
185 + }
186 + }
187 + }
188 +}
packages/react-noop-renderer/src/createReactNoop.js
+2 -2
@@ -26,7 +26,7 @@ import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
26 import enqueueTask from 'shared/enqueueTask';
27 import ReactSharedInternals from 'shared/ReactSharedInternals';
28 import warningWithoutStack from 'shared/warningWithoutStack';
29 -import {ConcurrentRoot, BatchedRoot, LegacyRoot} from 'shared/ReactRootTags';
29 +import {ConcurrentRoot, BlockingRoot, LegacyRoot} from 'shared/ReactRootTags';
30
31 type Container = {
32 rootID: string,
@@ -952,7 +952,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
952 };
953 const fiberRoot = NoopRenderer.createContainer(
954 container,
955 - BatchedRoot,
955 + BlockingRoot,
956 false,
957 null,
958 );
packages/react-reconciler/src/ReactFiber.js
+2 -2
@@ -36,7 +36,7 @@ import {
36 enableScopeAPI,
37 } from 'shared/ReactFeatureFlags';
38 import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
39 -import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
39 +import {ConcurrentRoot, BlockingRoot} from 'shared/ReactRootTags';
40 import {
41 IndeterminateComponent,
42 ClassComponent,
@@ -574,7 +574,7 @@ export function createHostRootFiber(tag: RootTag): Fiber {
574 let mode;
575 if (tag === ConcurrentRoot) {
576 mode = ConcurrentMode | BlockingMode | StrictMode;
577 - } else if (tag === BatchedRoot) {
577 + } else if (tag === BlockingRoot) {
578 mode = BlockingMode | StrictMode;
579 } else {
580 mode = NoMode;
packages/shared/ReactRootTags.js
+1 -1
@@ -10,5 +10,5 @@
10 export type RootTag = 0 | 1 | 2;
11
12 export const LegacyRoot = 0;
13 -export const BatchedRoot = 1;
13 +export const BlockingRoot = 1;
14 export const ConcurrentRoot = 2;