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

Remove unmountContainer

Instead, renderers should pass null to updateContainer.

Andrew Clark committed Dec 16, 2016 at 18:31 UTC da4ace152b5345bed7bb0e1fca33c8c47aeae70a
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
@@ -1559,6 +1559,7 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
1559 * does not update one component twice in a batch (#2410)
1560 * does not update one component twice in a batch (#6371)
1561 * unstable_batchedUpdates should return value from a callback
1562 +* unmounts and remounts a root in the same batch
1563
1564 src/renderers/shared/shared/__tests__/refs-destruction-test.js
1565 * should remove refs when destroying the parent
src/renderers/art/ReactARTFiber.js
+5 -1
@@ -357,7 +357,11 @@ class Surface extends Component {
357 }
358
359 componentWillUnmount() {
360 - ARTRenderer.unmountContainer(this._mountNode);
360 + ARTRenderer.updateContainer(
361 + null,
362 + this._mountNode,
363 + this,
364 + );
365 }
366
367 render() {
src/renderers/dom/fiber/ReactDOMFiber.js
+5 -9
@@ -259,7 +259,7 @@ function warnAboutUnstableUse() {
259 warned = true;
260 }
261
262 -function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
262 +function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, children : ReactNodeList, containerNode : DOMContainerElement | Document, callback: ?Function) {
263 let container : DOMContainerElement =
264 containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
265 let root;
@@ -268,9 +268,9 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
268 while (container.lastChild) {
269 container.removeChild(container.lastChild);
270 }
271 - root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
271 + root = container._reactRootContainer = DOMRenderer.mountContainer(children, container, parentComponent, callback);
272 } else {
273 - DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
273 + DOMRenderer.updateContainer(children, root = container._reactRootContainer, parentComponent, callback);
274 }
275 return DOMRenderer.getPublicRootInstance(root);
276 }
@@ -292,13 +292,9 @@ var ReactDOM = {
292
293 unmountComponentAtNode(container : DOMContainerElement) {
294 warnAboutUnstableUse();
295 - const root = container._reactRootContainer;
296 - if (root) {
297 - // TODO: Is it safe to reset this now or should I wait since this
298 - // unmount could be deferred?
295 + return renderSubtreeIntoContainer(null, null, container, () => {
296 container._reactRootContainer = null;
300 - DOMRenderer.unmountContainer(root);
301 - }
297 + });
298 },
299
300 findDOMNode: findDOMNode,
src/renderers/native/ReactNativeFiber.js
+4 -3
@@ -329,7 +329,7 @@ const NativeRenderer = ReactFiberReconciler({
329 // But creates an additional child Fiber for raw text children.
330 // No additional native views are created though.
331 // It's not clear to me which is better so I'm deferring for now.
332 - // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
332 + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
333 return false;
334 },
335
@@ -375,8 +375,9 @@ const ReactNative = {
375 const root = roots.get(containerTag);
376 if (root) {
377 // TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
378 - roots.delete(containerTag);
379 - NativeRenderer.unmountContainer(root);
378 + NativeRenderer.updateContainer(null, root, null, () => {
379 + roots.delete(containerTag);
380 + });
381 }
382 },
383
src/renderers/noop/ReactNoop.js
+4 -3
@@ -198,10 +198,11 @@ var ReactNoop = {
198
199 unmountRootWithID(rootID : string) {
200 const root = roots.get(rootID);
201 - roots.delete(rootID);
202 - rootContainers.delete(rootID);
201 if (root) {
204 - NoopRenderer.unmountContainer(root);
202 + NoopRenderer.updateContainer(null, root, null, () => {
203 + roots.delete(rootID);
204 + rootContainers.delete(rootID);
205 + });
206 }
207 },
208
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,
@@ -70,9 +71,8 @@ export type HostConfig<T, P, I, TI, C, CX> = {
71 };
72
73 export type Reconciler<C, I, TI> = {
73 - mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
74 - updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
75 - unmountContainer(container : OpaqueNode) : void,
74 + mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
75 + updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
76 performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
77 /* eslint-disable no-undef */
78 // FIXME: ESLint complains about type parameter
@@ -98,7 +98,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
98 module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
99
100 var {
101 - scheduleSetState,
101 + scheduleTopLevelSetState,
102 scheduleUpdateCallback,
103 performWithPriority,
104 batchedUpdates,
@@ -108,12 +108,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
108
109 return {
110
111 - mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
111 + mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
112 const context = getContextForSubtree(parentComponent);
113 const root = createFiberRoot(containerInfo, context);
114 const current = root.current;
115
116 - scheduleSetState(current, { element });
116 + scheduleTopLevelSetState(current, { element });
117 if (callback) {
118 scheduleUpdateCallback(current, callback);
119 }
@@ -128,32 +128,27 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
128 return current;
129 },
130
131 - updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
131 + updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
132 // TODO: If this is a nested container, this won't be the root.
133 const root : FiberRoot = (container.stateNode : any);
134 const current = root.current;
135
136 root.pendingContext = getContextForSubtree(parentComponent);
137
138 - scheduleSetState(current, { element });
138 + scheduleTopLevelSetState(current, { element });
139 if (callback) {
140 scheduleUpdateCallback(current, callback);
141 }
142
143 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
144 - ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
145 - }
146 - },
147 -
148 - unmountContainer(container : OpaqueNode) : void {
149 - // TODO: If this is a nested container, this won't be the root.
150 - const root : FiberRoot = (container.stateNode : any);
151 - const current = root.current;
152 -
153 - scheduleSetState(current, { element: [] });
154 -
155 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
156 - ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
143 + if (__DEV__) {
144 + if (ReactFiberInstrumentation.debugTool) {
145 + if (element === null) {
146 + ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
147 + } else {
148 + // This is an unmount
149 + ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
150 + }
151 + }
152 }
153 },
154
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);