ReactDOM.createRoot creates an async root (#11769)
Makes createRoot the opt-in API for async updates. Now we don't have to check the top-level element to see if it's an async container.
Andrew Clark committed
Dec 4, 2017 at 14:34 UTC
4d0e8fc4878569e7b7a70e9261a7c945cb0ff1d8
12 files changed
+60
-61
packages/react-cs-renderer/src/ReactNativeCS.js
+5
-1
@@ -265,7 +265,11 @@ const ReactCS = CSStatefulComponent({
265
let container = {
266
pendingChild: null,
267
};
268
- let root = ReactNativeCSFiberRenderer.createContainer(container, false);
268
+ let root = ReactNativeCSFiberRenderer.createContainer(
269
+ container,
270
+ false,
271
+ false,
272
+ );
273
return {root, container};
274
},
275
render({
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
+7
-19
@@ -69,26 +69,18 @@ describe('ReactDOMFiberAsync', () => {
69
ReactFeatureFlags = require('shared/ReactFeatureFlags');
70
container = document.createElement('div');
71
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
72
+ ReactFeatureFlags.enableCreateRoot = true;
73
ReactDOM = require('react-dom');
74
});
75
75
- it('AsyncComponent at the root makes the entire tree async', () => {
76
- ReactDOM.render(
77
- <AsyncComponent>
78
- <div>Hi</div>
79
- </AsyncComponent>,
80
- container,
81
- );
76
+ it('createRoot makes the entire tree async', () => {
77
+ const root = ReactDOM.createRoot(container);
78
+ root.render(<div>Hi</div>);
79
expect(container.textContent).toEqual('');
80
jest.runAllTimers();
81
expect(container.textContent).toEqual('Hi');
82
86
- ReactDOM.render(
87
- <AsyncComponent>
88
- <div>Bye</div>
89
- </AsyncComponent>,
90
- container,
91
- );
83
+ root.render(<div>Bye</div>);
84
expect(container.textContent).toEqual('Hi');
85
jest.runAllTimers();
86
expect(container.textContent).toEqual('Bye');
@@ -104,12 +96,8 @@ describe('ReactDOMFiberAsync', () => {
96
}
97
}
98
107
- ReactDOM.render(
108
- <AsyncComponent>
109
- <Component />
110
- </AsyncComponent>,
111
- container,
112
- );
99
+ const root = ReactDOM.createRoot(container);
100
+ root.render(<Component />);
101
expect(container.textContent).toEqual('');
102
jest.runAllTimers();
103
expect(container.textContent).toEqual('0');
packages/react-dom/src/__tests__/ReactDOMRoot-test.internal.js
+7
@@ -76,14 +76,17 @@ describe('ReactDOMRoot', () => {
76
it('renders children', () => {
77
const root = ReactDOM.createRoot(container);
78
root.render(<div>Hi</div>);
79
+ flush();
80
expect(container.textContent).toEqual('Hi');
81
});
82
83
it('unmounts children', () => {
84
const root = ReactDOM.createRoot(container);
85
root.render(<div>Hi</div>);
86
+ flush();
87
expect(container.textContent).toEqual('Hi');
88
root.unmount();
89
+ flush();
90
expect(container.textContent).toEqual('');
91
});
92
@@ -138,6 +141,7 @@ describe('ReactDOMRoot', () => {
141
<span />
142
</div>,
143
);
144
+ flush();
145
if (__DEV__) {
146
expect(console.error.calls.count()).toBe(0);
147
}
@@ -151,6 +155,7 @@ describe('ReactDOMRoot', () => {
155
<span />
156
</div>,
157
);
158
+ flush();
159
if (__DEV__) {
160
expect(console.error.calls.count()).toBe(1);
161
expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
@@ -167,6 +172,7 @@ describe('ReactDOMRoot', () => {
172
<span>d</span>
173
</div>,
174
);
175
+ flush();
176
expect(container.textContent).toEqual('abcd');
177
root.render(
178
<div>
@@ -174,6 +180,7 @@ describe('ReactDOMRoot', () => {
180
<span>c</span>
181
</div>,
182
);
183
+ flush();
184
expect(container.textContent).toEqual('abdc');
185
});
186
packages/react-dom/src/client/ReactDOM.js
+6
-5
@@ -376,8 +376,8 @@ type Root = {
376
_internalRoot: FiberRoot,
377
};
378
379
-function ReactRoot(container: Container, hydrate: boolean) {
380
- const root = DOMRenderer.createContainer(container, hydrate);
379
+function ReactRoot(container: Container, isAsync: boolean, hydrate: boolean) {
380
+ const root = DOMRenderer.createContainer(container, isAsync, hydrate);
381
this._internalRoot = root;
382
}
383
ReactRoot.prototype.render = function(
@@ -1031,8 +1031,9 @@ function legacyCreateRootFromDOMContainer(
1031
);
1032
}
1033
}
1034
- const root: Root = new ReactRoot(container, shouldHydrate);
1035
- return root;
1034
+ // Legacy roots are not async by default.
1035
+ const isAsync = false;
1036
+ return new ReactRoot(container, isAsync, shouldHydrate);
1037
}
1038
1039
function legacyRenderSubtreeIntoContainer(
@@ -1300,7 +1301,7 @@ if (enableCreateRoot) {
1301
options?: RootOptions,
1302
): ReactRoot {
1303
const hydrate = options != null && options.hydrate === true;
1303
- return new ReactRoot(container, hydrate);
1304
+ return new ReactRoot(container, true, hydrate);
1305
};
1306
}
1307
packages/react-native-renderer/src/ReactNativeRenderer.js
+5
-1
@@ -48,7 +48,11 @@ const ReactNativeRenderer: ReactNativeType = {
48
if (!root) {
49
// TODO (bvaughn): If we decide to keep the wrapper component,
50
// We could create a wrapper for containerTag as well to reduce special casing.
51
- root = ReactNativeFiberRenderer.createContainer(containerTag, false);
51
+ root = ReactNativeFiberRenderer.createContainer(
52
+ containerTag,
53
+ false,
54
+ false,
55
+ );
56
roots.set(containerTag, root);
57
}
58
ReactNativeFiberRenderer.updateContainer(element, root, null, callback);
packages/react-noop-renderer/src/ReactNoop.js
+2
-2
@@ -341,7 +341,7 @@ var ReactNoop = {
341
if (!root) {
342
const container = {rootID: rootID, children: []};
343
rootContainers.set(rootID, container);
344
- root = NoopRenderer.createContainer(container, false);
344
+ root = NoopRenderer.createContainer(container, true, false);
345
roots.set(rootID, root);
346
}
347
NoopRenderer.updateContainer(element, root, null, callback);
@@ -361,7 +361,7 @@ var ReactNoop = {
361
if (!root) {
362
const container = {rootID: rootID, children: []};
363
rootContainers.set(rootID, container);
364
- root = PersistentNoopRenderer.createContainer(container, false);
364
+ root = PersistentNoopRenderer.createContainer(container, true, false);
365
persistentRoots.set(rootID, root);
366
}
367
PersistentNoopRenderer.updateContainer(element, root, null, callback);
packages/react-reconciler/src/ReactFiber.js
+4
-4
@@ -35,7 +35,7 @@ import {
35
import getComponentName from 'shared/getComponentName';
36
37
import {NoWork} from './ReactFiberExpirationTime';
38
-import {NoContext} from './ReactTypeOfInternalContext';
38
+import {NoContext, AsyncUpdates} from './ReactTypeOfInternalContext';
39
40
if (__DEV__) {
41
var hasBadMapPolyfill = false;
@@ -288,9 +288,9 @@ export function createWorkInProgress(
288
return workInProgress;
289
}
290
291
-export function createHostRootFiber(): Fiber {
292
- const fiber = createFiber(HostRoot, null, NoContext);
293
- return fiber;
291
+export function createHostRootFiber(isAsync): Fiber {
292
+ const internalContextTag = isAsync ? AsyncUpdates : NoContext;
293
+ return createFiber(HostRoot, null, null, internalContextTag);
294
}
295
296
export function createFiberFromElement(
packages/react-reconciler/src/ReactFiberReconciler.js
+12
-25
@@ -12,7 +12,6 @@ import type {FiberRoot} from './ReactFiberRoot';
12
import type {ReactNodeList} from 'shared/ReactTypes';
13
import type {ExpirationTime} from './ReactFiberExpirationTime';
14
15
-import {enableAsyncSubtreeAPI} from 'shared/ReactFeatureFlags';
15
import {
16
findCurrentHostFiber,
17
findCurrentHostFiberWithNoPortals,
@@ -233,7 +232,11 @@ type DevToolsConfig<I, TI> = {|
232
|};
233
234
export type Reconciler<C, I, TI> = {
236
- createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot,
235
+ createContainer(
236
+ containerInfo: C,
237
+ isAsync: boolean,
238
+ hydrate: boolean,
239
+ ): OpaqueRoot,
240
updateContainer(
241
element: ReactNodeList,
242
container: OpaqueRoot,
@@ -288,7 +291,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
291
const {getPublicInstance} = config;
292
293
const {
291
- computeAsyncExpiration,
294
computeUniqueAsyncExpiration,
295
computeExpirationForFiber,
296
scheduleWork,
@@ -300,25 +302,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
302
deferredUpdates,
303
} = ReactFiberScheduler(config);
304
303
- function computeRootExpirationTime(current, element) {
304
- let expirationTime;
305
- // Check if the top-level element is an async wrapper component. If so,
306
- // treat updates to the root as async. This is a bit weird but lets us
307
- // avoid a separate `renderAsync` API.
308
- if (
309
- enableAsyncSubtreeAPI &&
310
- element != null &&
311
- (element: any).type != null &&
312
- (element: any).type.prototype != null &&
313
- (element: any).type.prototype.unstable_isAsyncReactComponent === true
314
- ) {
315
- expirationTime = computeAsyncExpiration();
316
- } else {
317
- expirationTime = computeExpirationForFiber(current);
318
- }
319
- return expirationTime;
320
- }
321
-
305
function scheduleRootUpdate(
306
current: Fiber,
307
element: ReactNodeList,
@@ -408,8 +391,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
391
}
392
393
return {
411
- createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot {
412
- return createFiberRoot(containerInfo, hydrate);
394
+ createContainer(
395
+ containerInfo: C,
396
+ isAsync: boolean,
397
+ hydrate: boolean,
398
+ ): OpaqueRoot {
399
+ return createFiberRoot(containerInfo, isAsync, hydrate);
400
},
401
402
updateContainer(
@@ -419,7 +406,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
406
callback: ?Function,
407
): ExpirationTime {
408
const current = container.current;
422
- const expirationTime = computeRootExpirationTime(current, element);
409
+ const expirationTime = computeExpirationForFiber(current);
410
return updateContainerAtExpirationTime(
411
element,
412
container,
packages/react-reconciler/src/ReactFiberRoot.js
+2
-1
@@ -52,11 +52,12 @@ export type FiberRoot = {
52
53
export function createFiberRoot(
54
containerInfo: any,
55
+ isAsync: boolean,
56
hydrate: boolean,
57
): FiberRoot {
58
// Cyclic construction. This cheats the type system right now because
59
// stateNode is any.
59
- const uninitializedFiber = createHostRootFiber();
60
+ const uninitializedFiber = createHostRootFiber(isAsync);
61
const root = {
62
current: uninitializedFiber,
63
containerInfo: containerInfo,
packages/react-reconciler/src/ReactFiberScheduler.js
-1
@@ -1738,7 +1738,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1738
}
1739
1740
return {
1741
- computeAsyncExpiration,
1741
computeExpirationForFiber,
1742
scheduleWork,
1743
requestWork,
packages/react-rt-renderer/src/ReactNativeRT.js
+5
-1
@@ -40,7 +40,11 @@ const ReactNativeRTFiber: ReactNativeRTType = {
40
if (!root) {
41
// TODO (bvaughn): If we decide to keep the wrapper component,
42
// We could create a wrapper for containerTag as well to reduce special casing.
43
- root = ReactNativeRTFiberRenderer.createContainer(containerTag, false);
43
+ root = ReactNativeRTFiberRenderer.createContainer(
44
+ containerTag,
45
+ false,
46
+ false,
47
+ );
48
roots.set(containerTag, root);
49
}
50
ReactNativeRTFiberRenderer.updateContainer(element, root, null, callback);
packages/react-test-renderer/src/ReactTestRenderer.js
+5
-1
@@ -572,7 +572,11 @@ const ReactTestRendererFiber = {
572
createNodeMock,
573
tag: 'CONTAINER',
574
};
575
- let root: FiberRoot | null = TestRenderer.createContainer(container, false);
575
+ let root: FiberRoot | null = TestRenderer.createContainer(
576
+ container,
577
+ false,
578
+ false,
579
+ );
580
invariant(root != null, 'something went wrong');
581
TestRenderer.updateContainer(element, root, null, null);
582