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

Remove unstable_asyncRender() API in favor of always using a wrapper

Andrew Clark committed Apr 18, 2017 at 14:46 UTC f69748601d5c1fd1fc3585d6d1d1970c2c13a272
6 files changed +142 -214
src/renderers/dom/fiber/ReactDOMFiber.js
+63 -106
@@ -387,7 +387,6 @@ function renderSubtreeIntoContainer(
387 parentComponent: ?ReactComponent<any, any, any>,
388 children: ReactNodeList,
389 containerNode: DOMContainerElement | Document,
390 - async: boolean,
390 callback: ?Function,
391 ) {
392 validateContainer(containerNode);
@@ -401,104 +400,84 @@ function renderSubtreeIntoContainer(
400 while (container.lastChild) {
401 container.removeChild(container.lastChild);
402 }
404 - const newRoot = async
405 - ? DOMRenderer.createAsyncContainer(container)
406 - : DOMRenderer.createContainer(container);
403 + const newRoot = DOMRenderer.createContainer(container);
404 root = (container._reactRootContainer = newRoot);
405 // Initial mount should not be batched.
406 DOMRenderer.unbatchedUpdates(() => {
407 DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
408 });
409 } else {
413 - if (async) {
414 - DOMRenderer.updateAsyncContainer(
415 - children,
416 - root,
417 - parentComponent,
418 - callback,
419 - );
420 - } else {
421 - DOMRenderer.updateContainer(children, root, parentComponent, callback);
422 - }
410 + DOMRenderer.updateContainer(children, root, parentComponent, callback);
411 }
412 return DOMRenderer.getPublicRootInstance(root);
413 }
414
427 -function render(
428 - element: ReactElement<any>,
429 - container: DOMContainerElement,
430 - async: boolean,
431 - callback: ?Function,
432 -) {
433 - validateContainer(container);
434 -
435 - if (ReactFeatureFlags.disableNewFiberFeatures) {
436 - // Top-level check occurs here instead of inside child reconciler because
437 - // because requirements vary between renderers. E.g. React Art
438 - // allows arrays.
439 - if (!isValidElement(element)) {
440 - if (typeof element === 'string') {
441 - invariant(
442 - false,
443 - 'ReactDOM.render(): Invalid component element. Instead of ' +
444 - "passing a string like 'div', pass " +
445 - "React.createElement('div') or <div />.",
446 - );
447 - } else if (typeof element === 'function') {
448 - invariant(
449 - false,
450 - 'ReactDOM.render(): Invalid component element. Instead of ' +
451 - 'passing a class like Foo, pass React.createElement(Foo) ' +
452 - 'or <Foo />.',
453 - );
454 - } else if (element != null && typeof element.props !== 'undefined') {
455 - // Check if it quacks like an element
456 - invariant(
457 - false,
458 - 'ReactDOM.render(): Invalid component element. This may be ' +
459 - 'caused by unintentionally loading two independent copies ' +
460 - 'of React.',
461 - );
462 - } else {
463 - invariant(false, 'ReactDOM.render(): Invalid component element.');
464 - }
465 - }
466 - }
467 -
468 - if (__DEV__) {
469 - const isRootRenderedBySomeReact = !!container._reactRootContainer;
470 - const rootEl = getReactRootElementInContainer(container);
471 - const hasNonRootReactChild = !!(rootEl &&
472 - ReactDOMComponentTree.getInstanceFromNode(rootEl));
473 -
474 - warning(
475 - !hasNonRootReactChild || isRootRenderedBySomeReact,
476 - 'render(...): Replacing React-rendered children with a new root ' +
477 - 'component. If you intended to update the children of this node, ' +
478 - 'you should instead have the existing children update their state ' +
479 - 'and render the new components instead of calling ReactDOM.render.',
480 - );
481 -
482 - warning(
483 - !container.tagName || container.tagName.toUpperCase() !== 'BODY',
484 - 'render(): Rendering components directly into document.body is ' +
485 - 'discouraged, since its children are often manipulated by third-party ' +
486 - 'scripts and browser extensions. This may lead to subtle ' +
487 - 'reconciliation issues. Try rendering into a container element created ' +
488 - 'for your app.',
489 - );
490 - }
491 -
492 - return renderSubtreeIntoContainer(null, element, container, async, callback);
493 -}
494 -
415 var ReactDOM = {
416 render(
417 element: ReactElement<any>,
418 container: DOMContainerElement,
419 callback: ?Function,
420 ) {
501 - return render(element, container, false, callback);
421 + validateContainer(container);
422 +
423 + if (ReactFeatureFlags.disableNewFiberFeatures) {
424 + // Top-level check occurs here instead of inside child reconciler because
425 + // because requirements vary between renderers. E.g. React Art
426 + // allows arrays.
427 + if (!isValidElement(element)) {
428 + if (typeof element === 'string') {
429 + invariant(
430 + false,
431 + 'ReactDOM.render(): Invalid component element. Instead of ' +
432 + "passing a string like 'div', pass " +
433 + "React.createElement('div') or <div />.",
434 + );
435 + } else if (typeof element === 'function') {
436 + invariant(
437 + false,
438 + 'ReactDOM.render(): Invalid component element. Instead of ' +
439 + 'passing a class like Foo, pass React.createElement(Foo) ' +
440 + 'or <Foo />.',
441 + );
442 + } else if (element != null && typeof element.props !== 'undefined') {
443 + // Check if it quacks like an element
444 + invariant(
445 + false,
446 + 'ReactDOM.render(): Invalid component element. This may be ' +
447 + 'caused by unintentionally loading two independent copies ' +
448 + 'of React.',
449 + );
450 + } else {
451 + invariant(false, 'ReactDOM.render(): Invalid component element.');
452 + }
453 + }
454 + }
455 +
456 + if (__DEV__) {
457 + const isRootRenderedBySomeReact = !!container._reactRootContainer;
458 + const rootEl = getReactRootElementInContainer(container);
459 + const hasNonRootReactChild = !!(rootEl &&
460 + ReactDOMComponentTree.getInstanceFromNode(rootEl));
461 +
462 + warning(
463 + !hasNonRootReactChild || isRootRenderedBySomeReact,
464 + 'render(...): Replacing React-rendered children with a new root ' +
465 + 'component. If you intended to update the children of this node, ' +
466 + 'you should instead have the existing children update their state ' +
467 + 'and render the new components instead of calling ReactDOM.render.',
468 + );
469 +
470 + warning(
471 + !container.tagName || container.tagName.toUpperCase() !== 'BODY',
472 + 'render(): Rendering components directly into document.body is ' +
473 + 'discouraged, since its children are often manipulated by third-party ' +
474 + 'scripts and browser extensions. This may lead to subtle ' +
475 + 'reconciliation issues. Try rendering into a container element created ' +
476 + 'for your app.',
477 + );
478 + }
479 +
480 + return renderSubtreeIntoContainer(null, element, container, callback);
481 },
482
483 unstable_renderSubtreeIntoContainer(
@@ -515,17 +494,10 @@ var ReactDOM = {
494 parentComponent,
495 element,
496 containerNode,
518 - false,
497 callback,
498 );
499 },
500
523 - unstable_asyncRender: (null: ?(
524 - element: ReactElement<any>,
525 - container: DOMContainerElement,
526 - callback: ?Function,
527 - ) => *),
528 -
501 unmountComponentAtNode(container: DOMContainerElement) {
502 invariant(
503 isValidContainer(container),
@@ -547,7 +519,7 @@ var ReactDOM = {
519
520 // Unmount should not be batched.
521 return DOMRenderer.unbatchedUpdates(() => {
550 - return renderSubtreeIntoContainer(null, null, container, false, () => {
522 + return renderSubtreeIntoContainer(null, null, container, () => {
523 container._reactRootContainer = null;
524 });
525 });
@@ -581,21 +553,6 @@ var ReactDOM = {
553 },
554 };
555
584 -if (ReactDOMFeatureFlags.enableAsyncSubtreeAPI) {
585 - ReactDOM.unstable_asyncRender = function(
586 - element: ReactElement<any>,
587 - container: DOMContainerElement,
588 - callback: ?Function,
589 - ) {
590 - return render(element, container, true, callback);
591 - };
592 -} else {
593 - // We set this to null on the ReactDOM export, then delete if the feature
594 - // flag is not enabled so that it's undiscoverable.
595 - // TODO: Is there a better way to satisfy Flow?
596 - delete ReactDOM.unstable_asyncRender;
597 -}
598 -
556 if (typeof injectInternals === 'function') {
557 injectInternals({
558 findFiberByHostInstance: ReactDOMComponentTree.getClosestInstanceFromNode,
src/renderers/dom/fiber/__tests__/ReactDOMFiberAsync-test.js
+37 -25
@@ -38,32 +38,19 @@ describe('ReactDOMFiberAsync', () => {
38 ReactDOM = require('react-dom');
39 });
40
41 - it('unstable_asyncRender creates an async tree', () => {
42 - ReactDOM.unstable_asyncRender(<div>Hi</div>, container);
43 - expect(container.textContent).toEqual('');
44 - jest.runAllTimers();
45 - expect(container.textContent).toEqual('Hi');
46 -
47 - ReactDOM.unstable_asyncRender(<div>Bye</div>, container);
48 - expect(container.textContent).toEqual('Hi');
49 - jest.runAllTimers();
50 - expect(container.textContent).toEqual('Bye');
51 - });
52 -
53 - it('updates inside an async tree are async by default', () => {
41 + it('unstable_asyncUpdates creates an async subtree', () => {
42 let instance;
43 class Component extends React.Component {
44 state = {step: 0};
45 + static unstable_asyncUpdates = true;
46 render() {
47 instance = this;
48 return <div>{this.state.step}</div>;
49 }
50 }
51
63 - ReactDOM.unstable_asyncRender(<Component />, container);
64 - expect(container.textContent).toEqual('');
52 + ReactDOM.render(<Component />, container);
53 jest.runAllTimers();
66 - expect(container.textContent).toEqual('0');
54
55 instance.setState({step: 1});
56 expect(container.textContent).toEqual('0');
@@ -71,11 +58,17 @@ describe('ReactDOMFiberAsync', () => {
58 expect(container.textContent).toEqual('1');
59 });
60
74 - it('unstable_asyncUpdates creates an async subtree', () => {
75 - let instance;
61 + it('updates inside an async subtree are async by default', () => {
62 class Component extends React.Component {
63 + static unstable_asyncUpdates = true;
64 + render() {
65 + return <Child />;
66 + }
67 + }
68 +
69 + let instance;
70 + class Child extends React.Component {
71 state = {step: 0};
78 - unstable_asyncUpdates = true;
72 render() {
73 instance = this;
74 return <div>{this.state.step}</div>;
@@ -91,16 +84,33 @@ describe('ReactDOMFiberAsync', () => {
84 expect(container.textContent).toEqual('1');
85 });
86
94 - it('updates inside an async subtree are async by default', () => {
95 - class Component extends React.Component {
96 - unstable_asyncUpdates = true;
87 + it('unstable_asyncUpdates at the root makes the entire tree async', () => {
88 + class Async extends React.Component {
89 + static unstable_asyncUpdates = true;
90 render() {
98 - return <Child />;
91 + return this.props.children;
92 }
93 }
94 + ReactDOM.render(<Async><div>Hi</div></Async>, container);
95 + expect(container.textContent).toEqual('');
96 + jest.runAllTimers();
97 + expect(container.textContent).toEqual('Hi');
98 +
99 + ReactDOM.render(<Async><div>Bye</div></Async>, container);
100 + expect(container.textContent).toEqual('Hi');
101 + jest.runAllTimers();
102 + expect(container.textContent).toEqual('Bye');
103 + });
104
105 + it('updates inside an async tree are async by default', () => {
106 + class Async extends React.Component {
107 + static unstable_asyncUpdates = true;
108 + render() {
109 + return this.props.children;
110 + }
111 + }
112 let instance;
103 - class Child extends React.Component {
113 + class Component extends React.Component {
114 state = {step: 0};
115 render() {
116 instance = this;
@@ -108,8 +118,10 @@ describe('ReactDOMFiberAsync', () => {
118 }
119 }
120
111 - ReactDOM.render(<Component />, container);
121 + ReactDOM.render(<Async><Component /></Async>, container);
122 + expect(container.textContent).toEqual('');
123 jest.runAllTimers();
124 + expect(container.textContent).toEqual('0');
125
126 instance.setState({step: 1});
127 expect(container.textContent).toEqual('0');
src/renderers/shared/fiber/ReactFiberBeginWork.js
+1 -1
@@ -66,7 +66,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
66 config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
67 hostContext: HostContext<C, CX>,
68 scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
69 - getPriorityContext: (fiber: Fiber) => PriorityLevel,
69 + getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
70 ) {
71 const {
72 shouldSetTextContent,
src/renderers/shared/fiber/ReactFiberClassComponent.js
+5 -5
@@ -60,7 +60,7 @@ if (__DEV__) {
60
61 module.exports = function(
62 scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
63 - getPriorityContext: (fiber: Fiber) => PriorityLevel,
63 + getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
64 memoizeProps: (workInProgress: Fiber, props: any) => void,
65 memoizeState: (workInProgress: Fiber, state: any) => void,
66 ) {
@@ -69,7 +69,7 @@ module.exports = function(
69 isMounted,
70 enqueueSetState(instance, partialState, callback) {
71 const fiber = ReactInstanceMap.get(instance);
72 - const priorityLevel = getPriorityContext(fiber);
72 + const priorityLevel = getPriorityContext(fiber, false);
73 callback = callback === undefined ? null : callback;
74 if (__DEV__) {
75 warnOnInvalidCallback(callback, 'setState');
@@ -79,7 +79,7 @@ module.exports = function(
79 },
80 enqueueReplaceState(instance, state, callback) {
81 const fiber = ReactInstanceMap.get(instance);
82 - const priorityLevel = getPriorityContext(fiber);
82 + const priorityLevel = getPriorityContext(fiber, false);
83 callback = callback === undefined ? null : callback;
84 if (__DEV__) {
85 warnOnInvalidCallback(callback, 'replaceState');
@@ -89,7 +89,7 @@ module.exports = function(
89 },
90 enqueueForceUpdate(instance, callback) {
91 const fiber = ReactInstanceMap.get(instance);
92 - const priorityLevel = getPriorityContext(fiber);
92 + const priorityLevel = getPriorityContext(fiber, false);
93 callback = callback === undefined ? null : callback;
94 if (__DEV__) {
95 warnOnInvalidCallback(callback, 'forceUpdate');
@@ -308,7 +308,7 @@ module.exports = function(
308 instance.refs = emptyObject;
309 instance.context = getMaskedContext(workInProgress, unmaskedContext);
310
311 - if (instance.unstable_asyncUpdates === true) {
311 + if (workInProgress.type.unstable_asyncUpdates === true) {
312 workInProgress.internalContextTag |= AsyncUpdates;
313 }
314
src/renderers/shared/fiber/ReactFiberReconciler.js
+29 -75
@@ -124,19 +124,12 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
124
125 export type Reconciler<C, I, TI> = {
126 createContainer(containerInfo: C): OpaqueRoot,
127 - createAsyncContainer(containerInfo: C): OpaqueRoot,
127 updateContainer(
128 element: ReactNodeList,
129 container: OpaqueRoot,
130 parentComponent: ?ReactComponent<any, any, any>,
131 callback: ?Function,
132 ): void,
134 - updateAsyncContainer(
135 - element: ReactNodeList,
136 - container: OpaqueRoot,
137 - parentComponent: ?ReactComponent<any, any, any>,
138 - callback: ?Function,
139 - ): void,
133 performWithPriority(priorityLevel: PriorityLevel, fn: Function): void,
134 batchedUpdates<A>(fn: () => A): A,
135 unbatchedUpdates<A>(fn: () => A): A,
@@ -193,7 +186,13 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
186 }
187 }
188
196 - const priorityLevel = getPriorityContext(current);
189 + // Check if the top-level element is an async wrapper component. If so, treat
190 + // updates to the root as async. This is a bit weird but lets us avoid a separate
191 + // `renderAsync` API.
192 + const forceAsync = element != null &&
193 + element.type != null &&
194 + (element.type: any).unstable_asyncUpdates === true;
195 + const priorityLevel = getPriorityContext(current, forceAsync);
196 const nextState = {element};
197 callback = callback === undefined ? null : callback;
198 if (__DEV__) {
@@ -208,85 +207,40 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
207 scheduleUpdate(current, priorityLevel);
208 }
209
211 - function updateContainer(
212 - element: ReactNodeList,
213 - container: OpaqueRoot,
214 - parentComponent: ?ReactComponent<any, any, any>,
215 - async: boolean,
216 - callback: ?Function,
217 - ) {
218 - // TODO: Make messages more user-friendly?
219 - if (__DEV__) {
220 - warning(
221 - !async || container.current.internalContextTag & AsyncUpdates,
222 - 'Attempted to schedule an asynchronous update on a sync container.',
223 - );
224 - }
225 -
226 - // TODO: If this is a nested container, this won't be the root.
227 - const current = container.current;
228 -
229 - if (__DEV__) {
230 - if (ReactFiberInstrumentation.debugTool) {
231 - if (current.alternate === null) {
232 - ReactFiberInstrumentation.debugTool.onMountContainer(container);
233 - } else if (element === null) {
234 - ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
235 - } else {
236 - ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
237 - }
238 - }
239 - }
240 -
241 - const context = getContextForSubtree(parentComponent);
242 - if (container.context === null) {
243 - container.context = context;
244 - } else {
245 - container.pendingContext = context;
246 - }
247 -
248 - scheduleTopLevelUpdate(current, element, callback);
249 - }
250 -
210 return {
211 createContainer(containerInfo: C): OpaqueRoot {
212 return createFiberRoot(containerInfo);
213 },
214
256 - createAsyncContainer(containerInfo: C): OpaqueRoot {
257 - const fiberRoot = createFiberRoot(containerInfo);
258 - fiberRoot.current.internalContextTag |= AsyncUpdates;
259 - return fiberRoot;
260 - },
261 -
215 updateContainer(
216 element: ReactNodeList,
217 container: OpaqueRoot,
218 parentComponent: ?ReactComponent<any, any, any>,
219 callback: ?Function,
220 ): void {
268 - updateContainer(
269 - element,
270 - container,
271 - parentComponent,
272 - false, // async = false
273 - callback,
274 - );
275 - },
221 + // TODO: If this is a nested container, this won't be the root.
222 + const current = container.current;
223 +
224 + if (__DEV__) {
225 + if (ReactFiberInstrumentation.debugTool) {
226 + if (current.alternate === null) {
227 + ReactFiberInstrumentation.debugTool.onMountContainer(container);
228 + } else if (element === null) {
229 + ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
230 + } else {
231 + ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
232 + }
233 + }
234 + }
235
277 - updateAsyncContainer(
278 - element: ReactNodeList,
279 - container: OpaqueRoot,
280 - parentComponent: ?ReactComponent<any, any, any>,
281 - callback: ?Function,
282 - ) {
283 - updateContainer(
284 - element,
285 - container,
286 - parentComponent,
287 - true, // async = true
288 - callback,
289 - );
236 + const context = getContextForSubtree(parentComponent);
237 + if (container.context === null) {
238 + container.context = context;
239 + } else {
240 + container.pendingContext = context;
241 + }
242 +
243 + scheduleTopLevelUpdate(current, element, callback);
244 },
245
246 performWithPriority,
src/renderers/shared/fiber/ReactFiberScheduler.js
+7 -2
@@ -1345,12 +1345,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
1345 }
1346 }
1347
1348 - function getPriorityContext(fiber: Fiber): PriorityLevel {
1348 + function getPriorityContext(
1349 + fiber: Fiber,
1350 + forceAsync: boolean,
1351 + ): PriorityLevel {
1352 let priorityLevel = priorityContext;
1353 if (priorityLevel === NoWork) {
1354 if (
1355 !useSyncScheduling ||
1353 - (enableAsyncSubtreeAPI === true && fiber.internalContextTag & AsyncUpdates)
1356 + (enableAsyncSubtreeAPI === true &&
1357 + fiber.internalContextTag & AsyncUpdates) ||
1358 + forceAsync
1359 ) {
1360 priorityLevel = LowPriority;
1361 } else {