@samitouri / QOS-React / commits / 4d0e8fc487

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