@samitouri / QOS-React / commits / a9365b97e5

Remove unmountContainer

Instead, renderers should pass null to updateContainer.

Andrew Clark committed Dec 16, 2016 at 18:31 UTC a9365b97e54c56616cd338efa2c1781f2637beb4
10 files changed +100 -46
scripts/fiber/tests-failing.txt
-1
@@ -90,7 +90,6 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
90 * throws in setState if the update callback is not a function
91 * throws in replaceState if the update callback is not a function
92 * throws in forceUpdate if the update callback is not a function
93 -* unmounts and remounts a root in the same batch
93
94 src/renderers/shared/shared/__tests__/refs-test.js
95 * Should increase refs with an increase in divs
scripts/fiber/tests-passing.txt
+1
@@ -1550,6 +1550,7 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
1550 * does not update one component twice in a batch (#2410)
1551 * does not update one component twice in a batch (#6371)
1552 * unstable_batchedUpdates should return value from a callback
1553 +* unmounts and remounts a root in the same batch
1554
1555 src/renderers/shared/shared/__tests__/refs-destruction-test.js
1556 * should remove refs when destroying the parent
src/renderers/art/ReactARTFiber.js
+5 -1
@@ -356,7 +356,11 @@ class Surface extends Component {
356 }
357
358 componentWillUnmount() {
359 - ARTRenderer.unmountContainer(this._mountNode);
359 + ARTRenderer.updateContainer(
360 + null,
361 + this._mountNode,
362 + this,
363 + );
364 }
365
366 render() {
src/renderers/dom/fiber/ReactDOMFiber.js
+5 -9
@@ -191,7 +191,7 @@ function warnAboutUnstableUse() {
191 warned = true;
192 }
193
194 -function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
194 +function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, children : ReactNodeList, containerNode : DOMContainerElement | Document, callback: ?Function) {
195 let container : DOMContainerElement =
196 containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
197 let root;
@@ -200,9 +200,9 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
200 while (container.lastChild) {
201 container.removeChild(container.lastChild);
202 }
203 - root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
203 + root = container._reactRootContainer = DOMRenderer.mountContainer(children, container, parentComponent, callback);
204 } else {
205 - DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
205 + DOMRenderer.updateContainer(children, root = container._reactRootContainer, parentComponent, callback);
206 }
207 return DOMRenderer.getPublicRootInstance(root);
208 }
@@ -224,13 +224,9 @@ var ReactDOM = {
224
225 unmountComponentAtNode(container : DOMContainerElement) {
226 warnAboutUnstableUse();
227 - const root = container._reactRootContainer;
228 - if (root) {
229 - // TODO: Is it safe to reset this now or should I wait since this
230 - // unmount could be deferred?
227 + return renderSubtreeIntoContainer(null, null, container, () => {
228 container._reactRootContainer = null;
232 - DOMRenderer.unmountContainer(root);
233 - }
229 + });
230 },
231
232 findDOMNode: findDOMNode,
src/renderers/native/ReactNativeFiber.js
+4 -3
@@ -326,7 +326,7 @@ const NativeRenderer = ReactFiberReconciler({
326 // But creates an additional child Fiber for raw text children.
327 // No additional native views are created though.
328 // It's not clear to me which is better so I'm deferring for now.
329 - // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
329 + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
330 return false;
331 },
332
@@ -372,8 +372,9 @@ const ReactNative = {
372 const root = roots.get(containerTag);
373 if (root) {
374 // TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
375 - roots.delete(containerTag);
376 - NativeRenderer.unmountContainer(root);
375 + NativeRenderer.updateContainer(null, root, null, () => {
376 + roots.delete(containerTag);
377 + });
378 }
379 },
380
src/renderers/noop/ReactNoop.js
+4 -3
@@ -188,10 +188,11 @@ var ReactNoop = {
188
189 unmountRootWithID(rootID : string) {
190 const root = roots.get(rootID);
191 - roots.delete(rootID);
192 - rootContainers.delete(rootID);
191 if (root) {
194 - NoopRenderer.unmountContainer(root);
192 + NoopRenderer.updateContainer(null, root, null, () => {
193 + roots.delete(rootID);
194 + rootContainers.delete(rootID);
195 + });
196 }
197 },
198
src/renderers/shared/fiber/ReactFiberReconciler.js
+17 -22
@@ -15,6 +15,7 @@
15 import type { Fiber } from 'ReactFiber';
16 import type { FiberRoot } from 'ReactFiberRoot';
17 import type { PriorityLevel } from 'ReactPriorityLevel';
18 +import type { ReactNodeList } from 'ReactTypes';
19
20 var {
21 findCurrentUnmaskedContext,
@@ -69,9 +70,8 @@ export type HostConfig<T, P, I, TI, C, CX> = {
70 };
71
72 export type Reconciler<C, I, TI> = {
72 - mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
73 - updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
74 - unmountContainer(container : OpaqueNode) : void,
73 + mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
74 + updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
75 performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
76 /* eslint-disable no-undef */
77 // FIXME: ESLint complains about type parameter
@@ -97,7 +97,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
97 module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
98
99 var {
100 - scheduleSetState,
100 + scheduleTopLevelSetState,
101 scheduleUpdateCallback,
102 performWithPriority,
103 batchedUpdates,
@@ -107,12 +107,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
107
108 return {
109
110 - mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
110 + mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
111 const context = getContextForSubtree(parentComponent);
112 const root = createFiberRoot(containerInfo, context);
113 const current = root.current;
114
115 - scheduleSetState(current, { element });
115 + scheduleTopLevelSetState(current, { element });
116 if (callback) {
117 scheduleUpdateCallback(current, callback);
118 }
@@ -127,32 +127,27 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
127 return current;
128 },
129
130 - updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
130 + updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
131 // TODO: If this is a nested container, this won't be the root.
132 const root : FiberRoot = (container.stateNode : any);
133 const current = root.current;
134
135 root.pendingContext = getContextForSubtree(parentComponent);
136
137 - scheduleSetState(current, { element });
137 + scheduleTopLevelSetState(current, { element });
138 if (callback) {
139 scheduleUpdateCallback(current, callback);
140 }
141
142 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
143 - ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
144 - }
145 - },
146 -
147 - unmountContainer(container : OpaqueNode) : void {
148 - // TODO: If this is a nested container, this won't be the root.
149 - const root : FiberRoot = (container.stateNode : any);
150 - const current = root.current;
151 -
152 - scheduleSetState(current, { element: [] });
153 -
154 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
155 - ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
142 + if (__DEV__) {
143 + if (ReactFiberInstrumentation.debugTool) {
144 + if (element === null) {
145 + ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
146 + } else {
147 + // This is an unmount
148 + ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
149 + }
150 + }
151 }
152 },
153
src/renderers/shared/fiber/ReactFiberScheduler.js
+8 -1
@@ -59,6 +59,7 @@ var {
59 addReplaceUpdate,
60 addForceUpdate,
61 addCallback,
62 + addTopLevelUpdate,
63 } = require('ReactFiberUpdateQueue');
64
65 var {
@@ -1058,6 +1059,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1059 scheduleUpdateAtPriority(fiber, priorityContext);
1060 }
1061
1062 + // TODO: This indirection will be removed as part of #8585
1063 + function scheduleTopLevelSetState(fiber : Fiber, partialState : any) {
1064 + addTopLevelUpdate(fiber, partialState, priorityContext);
1065 + scheduleUpdateAtPriority(fiber, priorityContext);
1066 + }
1067 +
1068 function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
1069 const previousPriorityContext = priorityContext;
1070 priorityContext = priorityLevel;
@@ -1105,7 +1112,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1112 }
1113
1114 return {
1108 - scheduleSetState: scheduleSetState,
1115 + scheduleTopLevelSetState: scheduleTopLevelSetState,
1116 scheduleUpdateCallback: scheduleUpdateCallback,
1117 performWithPriority: performWithPriority,
1118 batchedUpdates: batchedUpdates,
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+55 -6
@@ -39,6 +39,7 @@ type Update = {
39 callback: Callback | null,
40 isReplace: boolean,
41 isForced: boolean,
42 + isTopLevelUnmount: boolean,
43 next: Update | null,
44 };
45
@@ -141,6 +142,7 @@ function cloneUpdate(update : Update) : Update {
142 callback: update.callback,
143 isReplace: update.isReplace,
144 isForced: update.isForced,
145 + isTopLevelUnmount: update.isTopLevelUnmount,
146 next: null,
147 };
148 }
@@ -209,7 +211,9 @@ function findInsertionPosition(queue, update) : Update | null {
211 //
212 // However, if incoming update is inserted into the same position of both lists,
213 // we shouldn't make a copy.
212 -function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : void {
214 +//
215 +// If the update is cloned, it returns the cloned update.
216 +function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : Update | null {
217 const queue1 = ensureUpdateQueue(fiber);
218 const queue2 = fiber.alternate ? ensureUpdateQueue(fiber.alternate) : null;
219
@@ -244,7 +248,7 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
248 if (!queue2) {
249 // If there's no alternate queue, there's nothing else to do but insert.
250 insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
247 - return;
251 + return null;
252 }
253
254 // If there is an alternate queue, find the insertion position.
@@ -260,6 +264,7 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
264 // insert the clone into the alternate queue.
265 const update2 = cloneUpdate(update);
266 insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
267 + return update2;
268 } else {
269 // The insertion positions are the same, so when we inserted into the first
270 // queue, it also inserted into the alternate. All we need to do is update
@@ -272,6 +277,8 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
277 queue2.last = null;
278 }
279 }
280 +
281 + return null;
282 }
283
284 function addUpdate(
@@ -285,6 +292,7 @@ function addUpdate(
292 callback: null,
293 isReplace: false,
294 isForced: false,
295 + isTopLevelUnmount: false,
296 next: null,
297 };
298 if (__DEV__) {
@@ -306,6 +314,7 @@ function addReplaceUpdate(
314 callback: null,
315 isReplace: true,
316 isForced: false,
317 + isTopLevelUnmount: false,
318 next: null,
319 };
320
@@ -324,6 +333,7 @@ function addForceUpdate(fiber : Fiber, priorityLevel : PriorityLevel) : void {
333 callback: null,
334 isReplace: false,
335 isForced: true,
336 + isTopLevelUnmount: false,
337 next: null,
338 };
339 if (__DEV__) {
@@ -342,6 +352,7 @@ function addCallback(fiber : Fiber, callback: Callback, priorityLevel : Priority
352 callback,
353 isReplace: false,
354 isForced: false,
355 + isTopLevelUnmount: false,
356 next: null,
357 };
358 insertUpdate(fiber, update);
@@ -353,6 +364,42 @@ function getPendingPriority(queue : UpdateQueue) : PriorityLevel {
364 }
365 exports.getPendingPriority = getPendingPriority;
366
367 +function addTopLevelUpdate(
368 + fiber : Fiber,
369 + partialState : PartialState<any, any>,
370 + priorityLevel : PriorityLevel
371 +) : void {
372 + const isTopLevelUnmount = partialState === null;
373 +
374 + const update = {
375 + priorityLevel,
376 + partialState,
377 + callback: null,
378 + isReplace: false,
379 + isForced: false,
380 + isTopLevelUnmount,
381 + next: null,
382 + };
383 + const update2 = insertUpdate(fiber, update);
384 +
385 + if (isTopLevelUnmount) {
386 + // Drop all updates that are lower-priority, so that the tree is not
387 + // remounted. We need to do this for both queues.
388 + const queue1 = fiber.updateQueue;
389 + const queue2 = fiber.alternate && fiber.alternate.updateQueue;
390 +
391 + if (queue1 && update.next) {
392 + update.next = null;
393 + queue1.last = update;
394 + }
395 + if (queue2 && update2 && update2.next) {
396 + update2.next = null;
397 + queue2.last = update;
398 + }
399 + }
400 +}
401 +exports.addTopLevelUpdate = addTopLevelUpdate;
402 +
403 function getStateFromUpdate(update, instance, prevState, props) {
404 const partialState = update.partialState;
405 if (typeof partialState === 'function') {
@@ -412,12 +459,14 @@ function beginUpdateQueue(
459 if (update.isForced) {
460 queue.hasForceUpdate = true;
461 }
415 - if (update.callback) {
462 + // Second condition ignores top-level unmount callbacks if they are not the
463 + // last update in the queue, since a subsequent update will cause a remount.
464 + if (update.callback && !(update.isTopLevelUnmount && update.next)) {
465 + const callbackUpdate = cloneUpdate(update);
466 if (callbackList && callbackList.last) {
417 - callbackList.last.next = update;
418 - callbackList.last = update;
467 + callbackList.last.next = callbackUpdate;
468 + callbackList.last = callbackUpdate;
469 } else {
420 - const callbackUpdate = cloneUpdate(update);
470 callbackList = {
471 first: callbackUpdate,
472 last: callbackUpdate,
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
+1
@@ -654,6 +654,7 @@ describe('ReactIncrementalErrorHandling', () => {
654 ReactNoop.unmountRootWithID('d');
655 ReactNoop.unmountRootWithID('e');
656 ReactNoop.unmountRootWithID('f');
657 + ReactNoop.flush();
658 expect(ReactNoop.getChildren('a')).toEqual(null);
659 expect(ReactNoop.getChildren('b')).toEqual(null);
660 expect(ReactNoop.getChildren('c')).toEqual(null);