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;