@samitouri / QOS-React / commits / a0af1e1317

Remove ability to resume work

The current implementation of resuming work is buggy. The underlying model is also flawed. Rather than attempt to fix a flawed model, we'll scrap the feature entirely and add it back later.

Andrew Clark committed Jun 19, 2017 at 14:58 UTC a0af1e13174035c3b4b3f2b911bfccaf4d3ba41d
8 files changed +310 -491
src/renderers/noop/ReactNoopEntry.js
+12 -12
@@ -430,18 +430,18 @@ var ReactNoop = {
430 if (fiber.updateQueue) {
431 logUpdateQueue(fiber.updateQueue, depth);
432 }
433 - const childInProgress = fiber.progressedChild;
434 - if (childInProgress && childInProgress !== fiber.child) {
435 - log(
436 - ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority,
437 - );
438 - logFiber(childInProgress, depth + 1);
439 - if (fiber.child) {
440 - log(' '.repeat(depth + 1) + 'CURRENT');
441 - }
442 - } else if (fiber.child && fiber.updateQueue) {
443 - log(' '.repeat(depth + 1) + 'CHILDREN');
444 - }
433 + // const childInProgress = fiber.progressedChild;
434 + // if (childInProgress && childInProgress !== fiber.child) {
435 + // log(
436 + // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
437 + // );
438 + // logFiber(childInProgress, depth + 1);
439 + // if (fiber.child) {
440 + // log(' '.repeat(depth + 1) + 'CURRENT');
441 + // }
442 + // } else if (fiber.child && fiber.updateQueue) {
443 + // log(' '.repeat(depth + 1) + 'CHILDREN');
444 + // }
445 if (fiber.child) {
446 logFiber(fiber.child, depth + 1);
447 }
src/renderers/shared/fiber/ReactChildFiber.js
+28 -39
@@ -74,7 +74,7 @@ if (__DEV__) {
74 }
75
76 const {
77 - cloneFiber,
77 + createWorkInProgress,
78 createFiberFromElement,
79 createFiberFromFragment,
80 createFiberFromText,
@@ -207,12 +207,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
207 childToDelete = childToDelete.alternate;
208 }
209 // Deletions are added in reversed order so we add it to the front.
210 - const last = returnFiber.progressedLastDeletion;
210 + // At this point, the return fiber's effect list is empty except for
211 + // deletions, so we can just append the deletion to the list. The remaining
212 + // effects aren't added until the complete phase. Once we implement
213 + // resuming, this may not be true.
214 + const last = returnFiber.lastEffect;
215 if (last !== null) {
216 last.nextEffect = childToDelete;
213 - returnFiber.progressedLastDeletion = childToDelete;
217 + returnFiber.lastEffect = childToDelete;
218 } else {
215 - returnFiber.progressedFirstDeletion = returnFiber.progressedLastDeletion = childToDelete;
219 + returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
220 }
221 childToDelete.nextEffect = null;
222 childToDelete.effectTag = Deletion;
@@ -262,7 +266,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
266 // We currently set sibling to null and index to 0 here because it is easy
267 // to forget to do before returning it. E.g. for the single child case.
268 if (shouldClone) {
265 - const clone = cloneFiber(fiber, priority);
269 + const clone = createWorkInProgress(fiber, priority);
270 clone.index = 0;
271 clone.sibling = null;
272 return clone;
@@ -1445,44 +1449,29 @@ exports.mountChildFibersInPlace = ChildReconciler(false, false);
1449 exports.cloneChildFibers = function(
1450 current: Fiber | null,
1451 workInProgress: Fiber,
1452 + renderPriority: PriorityLevel,
1453 ): void {
1449 - if (!workInProgress.child) {
1454 + invariant(
1455 + current === null || workInProgress.child === current.child,
1456 + 'Resuming work not yet implemented.',
1457 + );
1458 +
1459 + if (workInProgress.child === null) {
1460 return;
1461 }
1452 - if (current !== null && workInProgress.child === current.child) {
1453 - // We use workInProgress.child since that lets Flow know that it can't be
1454 - // null since we validated that already. However, as the line above suggests
1455 - // they're actually the same thing.
1456 - let currentChild = workInProgress.child;
1457 - // TODO: This used to reset the pending priority. Not sure if that is needed.
1458 - // workInProgress.pendingWorkPriority = current.pendingWorkPriority;
1459 - // TODO: The below priority used to be set to NoWork which would've
1460 - // dropped work. This is currently unobservable but will become
1461 - // observable when the first sibling has lower priority work remaining
1462 - // than the next sibling. At that point we should add tests that catches
1463 - // this.
1464 - let newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
1465 - workInProgress.child = newChild;
1462
1463 + let currentChild = workInProgress.child;
1464 + let newChild = createWorkInProgress(currentChild, renderPriority);
1465 + workInProgress.child = newChild;
1466 +
1467 + newChild.return = workInProgress;
1468 + while (currentChild.sibling !== null) {
1469 + currentChild = currentChild.sibling;
1470 + newChild = newChild.sibling = createWorkInProgress(
1471 + currentChild,
1472 + renderPriority,
1473 + );
1474 newChild.return = workInProgress;
1468 - while (currentChild.sibling !== null) {
1469 - currentChild = currentChild.sibling;
1470 - newChild = newChild.sibling = cloneFiber(
1471 - currentChild,
1472 - currentChild.pendingWorkPriority,
1473 - );
1474 - newChild.return = workInProgress;
1475 - }
1476 - newChild.sibling = null;
1477 - } else {
1478 - // If there is no alternate, then we don't need to clone the children.
1479 - // If the children of the alternate fiber is a different set, then we don't
1480 - // need to clone. We need to reset the return fiber though since we'll
1481 - // traverse down into them.
1482 - let child = workInProgress.child;
1483 - while (child !== null) {
1484 - child.return = workInProgress;
1485 - child = child.sibling;
1486 - }
1475 }
1476 + newChild.sibling = null;
1477 };
src/renderers/shared/fiber/ReactFiber.js
+47 -77
@@ -44,8 +44,6 @@ var {NoContext} = require('ReactTypeOfInternalContext');
44
45 var {NoEffect} = require('ReactTypeOfSideEffect');
46
47 -var {cloneUpdateQueue} = require('ReactFiberUpdateQueue');
48 -
47 var invariant = require('fbjs/lib/invariant');
48
49 if (__DEV__) {
@@ -118,7 +116,6 @@ export type Fiber = {|
116
117 // Input is the data coming into process this fiber. Arguments. Props.
118 pendingProps: any, // This type will be more specific once we overload the tag.
121 - // TODO: I think that there is a way to merge pendingProps and memoizedProps.
119 memoizedProps: any, // The props used to create the output.
120
121 // A queue of state updates and callbacks.
@@ -150,24 +147,6 @@ export type Fiber = {|
147 // This will be used to quickly determine if a subtree has no pending changes.
148 pendingWorkPriority: PriorityLevel,
149
153 - // This value represents the priority level that was last used to process this
154 - // component. This indicates whether it is better to continue from the
155 - // progressed work or if it is better to continue from the current state.
156 - progressedPriority: PriorityLevel,
157 -
158 - // If work bails out on a Fiber that already had some work started at a lower
159 - // priority, then we need to store the progressed work somewhere. This holds
160 - // the started child set until we need to get back to working on it. It may
161 - // or may not be the same as the "current" child.
162 - progressedChild: Fiber | null,
163 -
164 - // When we reconcile children onto progressedChild it is possible that we have
165 - // to delete some child fibers. We need to keep track of this side-effects so
166 - // that if we continue later on, we have to include those effects. Deletions
167 - // are added in the reverse order from sibling pointers.
168 - progressedFirstDeletion: Fiber | null,
169 - progressedLastDeletion: Fiber | null,
170 -
150 // This is a pooled version of a Fiber. Every fiber that gets updated will
151 // eventually have a pair. There are cases when we can clean up pairs to save
152 // memory if we need to.
@@ -234,10 +213,6 @@ var createFiber = function(
213 lastEffect: null,
214
215 pendingWorkPriority: NoWork,
237 - progressedPriority: NoWork,
238 - progressedChild: null,
239 - progressedFirstDeletion: null,
240 - progressedLastDeletion: null,
216
217 alternate: null,
218 };
@@ -260,66 +235,61 @@ function shouldConstruct(Component) {
235 }
236
237 // This is used to create an alternate fiber to do work on.
263 -// TODO: Rename to createWorkInProgressFiber or something like that.
264 -exports.cloneFiber = function(
265 - fiber: Fiber,
266 - priorityLevel: PriorityLevel,
238 +exports.createWorkInProgress = function(
239 + current: Fiber,
240 + renderPriority: PriorityLevel,
241 ): Fiber {
268 - // We clone to get a work in progress. That means that this fiber is the
269 - // current. To make it safe to reuse that fiber later on as work in progress
270 - // we need to reset its work in progress flag now. We don't have an
271 - // opportunity to do this earlier since we don't traverse the tree when
272 - // the work in progress tree becomes the current tree.
273 - // fiber.progressedPriority = NoWork;
274 - // fiber.progressedChild = null;
275 -
276 - // We use a double buffering pooling technique because we know that we'll only
277 - // ever need at most two versions of a tree. We pool the "other" unused node
278 - // that we're free to reuse. This is lazily created to avoid allocating extra
279 - // objects for things that are never updated. It also allow us to reclaim the
280 - // extra memory if needed.
281 - let alt = fiber.alternate;
282 - if (alt !== null) {
283 - // If we clone, then we do so from the "current" state. The current state
284 - // can't have any side-effects that are still valid so we reset just to be
285 - // sure.
286 - alt.effectTag = NoEffect;
287 - alt.nextEffect = null;
288 - alt.firstEffect = null;
289 - alt.lastEffect = null;
290 - } else {
291 - // This should not have an alternate already
292 - alt = createFiber(fiber.tag, fiber.key, fiber.internalContextTag);
293 - alt.type = fiber.type;
242 + let workInProgress = current.alternate;
243 + if (workInProgress === null) {
244 + // We use a double buffering pooling technique because we know that we'll
245 + // only ever need at most two versions of a tree. We pool the "other" unused
246 + // node that we're free to reuse. This is lazily created to avoid allocating
247 + // extra objects for things that are never updated. It also allow us to
248 + // reclaim the extra memory if needed.
249 + workInProgress = createFiber(
250 + current.tag,
251 + current.key,
252 + current.internalContextTag,
253 + );
254 + workInProgress.type = current.type;
255 + workInProgress.stateNode = current.stateNode;
256
295 - alt.progressedChild = fiber.progressedChild;
296 - alt.progressedPriority = fiber.progressedPriority;
257 + if (__DEV__) {
258 + // DEV-only fields
259 + workInProgress._debugID = current._debugID;
260 + workInProgress._debugSource = current._debugSource;
261 + workInProgress._debugOwner = current._debugOwner;
262 + }
263
298 - alt.alternate = fiber;
299 - fiber.alternate = alt;
264 + workInProgress.alternate = current;
265 + current.alternate = workInProgress;
266 + } else {
267 + // We already have an alternate.
268 + // Reset the effect tag.
269 + workInProgress.effectTag = NoWork;
270 +
271 + // The effect list is no longer valid.
272 + workInProgress.nextEffect = null;
273 + workInProgress.firstEffect = null;
274 + workInProgress.lastEffect = null;
275 }
276
302 - alt.stateNode = fiber.stateNode;
303 - alt.child = fiber.child;
304 - alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
305 - alt.index = fiber.index; // This should always be overridden.
306 - alt.ref = fiber.ref;
307 - // pendingProps is here for symmetry but is unnecessary in practice for now.
308 - // TODO: Pass in the new pendingProps as an argument maybe?
309 - alt.pendingProps = fiber.pendingProps;
310 - cloneUpdateQueue(fiber, alt);
311 - alt.pendingWorkPriority = priorityLevel;
277 + workInProgress.pendingWorkPriority = renderPriority;
278
313 - alt.memoizedProps = fiber.memoizedProps;
314 - alt.memoizedState = fiber.memoizedState;
279 + workInProgress.child = current.child;
280 + workInProgress.memoizedProps = current.memoizedProps;
281 + workInProgress.memoizedState = current.memoizedState;
282 + workInProgress.updateQueue = current.updateQueue;
283
316 - if (__DEV__) {
317 - alt._debugID = fiber._debugID;
318 - alt._debugSource = fiber._debugSource;
319 - alt._debugOwner = fiber._debugOwner;
320 - }
284 + // pendingProps is set by the parent during reconciliation.
285 + // TODO: Pass this as an argument.
286 +
287 + // These will be overridden during the parent's reconciliation
288 + workInProgress.sibling = current.sibling;
289 + workInProgress.index = current.index;
290 + workInProgress.ref = current.ref;
291
322 - return alt;
292 + return workInProgress;
293 };
294
295 exports.createHostRootFiber = function(): Fiber {
src/renderers/shared/fiber/ReactFiberBeginWork.js
+12 -142
@@ -49,7 +49,7 @@ var {
49 YieldComponent,
50 Fragment,
51 } = ReactTypeOfWork;
52 -var {NoWork, OffscreenPriority} = require('ReactPriorityLevel');
52 +var {NoWork} = require('ReactPriorityLevel');
53 var {
54 PerformedWork,
55 Placement,
@@ -76,11 +76,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
76 scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
77 getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
78 ) {
79 - const {
80 - shouldSetTextContent,
81 - useSyncScheduling,
82 - shouldDeprioritizeSubtree,
83 - } = config;
79 + const {shouldSetTextContent} = config;
80
81 const {pushHostContext, pushHostContainer} = hostContext;
82
@@ -94,7 +90,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
90 adoptClassInstance,
91 constructClassInstance,
92 mountClassInstance,
97 - resumeMountClassInstance,
93 + // resumeMountClassInstance,
94 updateClassInstance,
95 } = ReactFiberClassComponent(
96 scheduleUpdate,
@@ -103,28 +99,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
99 memoizeState,
100 );
101
106 - function markChildAsProgressed(current, workInProgress, priorityLevel) {
107 - // We now have clones. Let's store them as the currently progressed work.
108 - workInProgress.progressedChild = workInProgress.child;
109 - workInProgress.progressedPriority = priorityLevel;
110 - if (current !== null) {
111 - // We also store it on the current. When the alternate swaps in we can
112 - // continue from this point.
113 - current.progressedChild = workInProgress.progressedChild;
114 - current.progressedPriority = workInProgress.progressedPriority;
115 - }
116 - }
117 -
118 - function clearDeletions(workInProgress) {
119 - workInProgress.progressedFirstDeletion = workInProgress.progressedLastDeletion = null;
120 - }
121 -
122 - function transferDeletions(workInProgress) {
123 - // Any deletions get added first into the effect list.
124 - workInProgress.firstEffect = workInProgress.progressedFirstDeletion;
125 - workInProgress.lastEffect = workInProgress.progressedLastDeletion;
126 - }
127 -
102 function reconcileChildren(current, workInProgress, nextChildren) {
103 const priorityLevel = workInProgress.pendingWorkPriority;
104 reconcileChildrenAtPriority(
@@ -141,9 +115,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
115 nextChildren,
116 priorityLevel,
117 ) {
144 - // At this point any memoization is no longer valid since we'll have changed
145 - // the children.
146 - workInProgress.memoizedProps = null;
118 if (current === null) {
119 // If this is a fresh new component that hasn't been rendered yet, we
120 // won't update its child set by applying minimal side-effects. Instead,
@@ -162,16 +133,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
133
134 // If we had any progressed work already, that is invalid at this point so
135 // let's throw it out.
165 - clearDeletions(workInProgress);
166 -
136 workInProgress.child = reconcileChildFibers(
137 workInProgress,
138 workInProgress.child,
139 nextChildren,
140 priorityLevel,
141 );
173 -
174 - transferDeletions(workInProgress);
142 } else {
143 // If, on the other hand, it is already using a clone, that means we've
144 // already begun some work on this tree and we can continue where we left
@@ -182,10 +149,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
149 nextChildren,
150 priorityLevel,
151 );
185 -
186 - transferDeletions(workInProgress);
152 }
188 - markChildAsProgressed(current, workInProgress, priorityLevel);
153 }
154
155 function updateFragment(current, workInProgress) {
@@ -280,8 +244,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
244 mountClassInstance(workInProgress, priorityLevel);
245 shouldUpdate = true;
246 } else {
247 + invariant(false, 'Resuming work not yet implemented.');
248 // In a resume, we'll already have an instance we can reuse.
284 - shouldUpdate = resumeMountClassInstance(workInProgress, priorityLevel);
249 + // shouldUpdate = resumeMountClassInstance(workInProgress, priorityLevel);
250 }
251 } else {
252 shouldUpdate = updateClassInstance(
@@ -357,6 +322,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
322 if (updateQueue !== null) {
323 const prevState = workInProgress.memoizedState;
324 const state = beginUpdateQueue(
325 + current,
326 workInProgress,
327 updateQueue,
328 null,
@@ -395,7 +361,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
361 element,
362 priorityLevel,
363 );
398 - markChildAsProgressed(current, workInProgress, priorityLevel);
364 } else {
365 // Otherwise reset hydration state in case we aborted and resumed another
366 // root.
@@ -433,27 +398,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
398 );
399 }
400 } else if (nextProps === null || memoizedProps === nextProps) {
436 - if (
437 - !useSyncScheduling &&
438 - shouldDeprioritizeSubtree(type, memoizedProps) &&
439 - workInProgress.pendingWorkPriority !== OffscreenPriority
440 - ) {
441 - // This subtree still has work, but it should be deprioritized so we need
442 - // to bail out and not do any work yet.
443 - // TODO: It would be better if this tree got its correct priority set
444 - // during scheduleUpdate instead because otherwise we'll start a higher
445 - // priority reconciliation first before we can get down here. However,
446 - // that is a bit tricky since workInProgress and current can have
447 - // different "hidden" settings.
448 - let child = workInProgress.progressedChild;
449 - while (child !== null) {
450 - // To ensure that this subtree gets its priority reset, the children
451 - // need to be reset.
452 - child.pendingWorkPriority = OffscreenPriority;
453 - child = child.sibling;
454 - }
455 - return null;
456 - }
401 return bailoutOnAlreadyFinishedWork(current, workInProgress);
402 }
403
@@ -474,55 +418,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
418
419 markRef(current, workInProgress);
420
477 - if (
478 - !useSyncScheduling &&
479 - shouldDeprioritizeSubtree(workInProgress.type, nextProps) &&
480 - workInProgress.pendingWorkPriority !== OffscreenPriority
481 - ) {
482 - // If this host component is hidden, we can bail out on the children.
483 - // We'll rerender the children later at the lower priority.
484 -
485 - // It is unfortunate that we have to do the reconciliation of these
486 - // children already since that will add them to the tree even though
487 - // they are not actually done yet. If this is a large set it is also
488 - // confusing that this takes time to do right now instead of later.
489 -
490 - if (workInProgress.progressedPriority === OffscreenPriority) {
491 - // If we already made some progress on the offscreen priority before,
492 - // then we should continue from where we left off.
493 - workInProgress.child = workInProgress.progressedChild;
494 - }
495 -
496 - // Reconcile the children and stash them for later work.
497 - reconcileChildrenAtPriority(
498 - current,
499 - workInProgress,
500 - nextChildren,
501 - OffscreenPriority,
502 - );
503 - memoizeProps(workInProgress, nextProps);
504 - workInProgress.child = current !== null ? current.child : null;
505 -
506 - if (current === null) {
507 - // If this doesn't have a current we won't track it for placement
508 - // effects. However, when we come back around to this we have already
509 - // inserted the parent which means that we'll infact need to make this a
510 - // placement.
511 - // TODO: There has to be a better solution to this problem.
512 - let child = workInProgress.progressedChild;
513 - while (child !== null) {
514 - child.effectTag = Placement;
515 - child = child.sibling;
516 - }
517 - }
518 -
519 - // Abort and don't process children yet.
520 - return null;
521 - } else {
522 - reconcileChildren(current, workInProgress, nextChildren);
523 - memoizeProps(workInProgress, nextProps);
524 - return workInProgress.child;
525 - }
421 + reconcileChildren(current, workInProgress, nextChildren);
422 + memoizeProps(workInProgress, nextProps);
423 + return workInProgress.child;
424 }
425
426 function updateHostText(current, workInProgress) {
@@ -647,10 +545,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
545
546 // The following is a fork of reconcileChildrenAtPriority but using
547 // stateNode to store the child.
650 -
651 - // At this point any memoization is no longer valid since we'll have changed
652 - // the children.
653 - workInProgress.memoizedProps = null;
548 if (current === null) {
549 workInProgress.stateNode = mountChildFibersInPlace(
550 workInProgress,
@@ -659,16 +553,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
553 priorityLevel,
554 );
555 } else if (current.child === workInProgress.child) {
662 - clearDeletions(workInProgress);
663 -
556 workInProgress.stateNode = reconcileChildFibers(
557 workInProgress,
558 workInProgress.stateNode,
559 nextChildren,
560 priorityLevel,
561 );
670 -
671 - transferDeletions(workInProgress);
562 } else {
563 workInProgress.stateNode = reconcileChildFibersInPlace(
564 workInProgress,
@@ -676,8 +566,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
566 nextChildren,
567 priorityLevel,
568 );
679 -
680 - transferDeletions(workInProgress);
569 }
570
571 memoizeProps(workInProgress, nextCoroutine);
@@ -721,7 +609,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
609 priorityLevel,
610 );
611 memoizeProps(workInProgress, nextChildren);
724 - markChildAsProgressed(current, workInProgress, priorityLevel);
612 } else {
613 reconcileChildren(current, workInProgress, nextChildren);
614 memoizeProps(workInProgress, nextChildren);
@@ -756,7 +643,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
643 cancelWorkTimer(workInProgress);
644 }
645
759 - const priorityLevel = workInProgress.pendingWorkPriority;
646 // TODO: We should ideally be able to bail out early if the children have no
647 // more work to do. However, since we don't have a separation of this
648 // Fiber's priority and its children yet - we don't know without doing lots
@@ -771,14 +657,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
657 // return null;
658 // }
659
774 - if (current && workInProgress.child === current.child) {
775 - // If we had any progressed work already, that is invalid at this point so
776 - // let's throw it out.
777 - clearDeletions(workInProgress);
778 - }
779 -
780 - cloneChildFibers(current, workInProgress);
781 - markChildAsProgressed(current, workInProgress, priorityLevel);
660 + // TODO: Pass the priority as an argument
661 + const renderPriority = workInProgress.pendingWorkPriority;
662 + cloneChildFibers(current, workInProgress, renderPriority);
663 return workInProgress.child;
664 }
665
@@ -833,17 +714,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
714 ReactDebugCurrentFiber.current = workInProgress;
715 }
716
836 - // If we don't bail out, we're going be recomputing our children so we need
837 - // to drop our effect list.
838 - workInProgress.firstEffect = null;
839 - workInProgress.lastEffect = null;
840 -
841 - if (workInProgress.progressedPriority === priorityLevel) {
842 - // If we have progressed work on this priority level already, we can
843 - // proceed this that as the child.
844 - workInProgress.child = workInProgress.progressedChild;
845 - }
846 -
717 switch (workInProgress.tag) {
718 case IndeterminateComponent:
719 return mountIndeterminateComponent(
src/renderers/shared/fiber/ReactFiberClassComponent.js
+111 -107
@@ -362,6 +362,8 @@ module.exports = function(
362 workInProgress: Fiber,
363 priorityLevel: PriorityLevel,
364 ): void {
365 + const current = workInProgress.alternate;
366 +
367 if (__DEV__) {
368 checkClassInstance(workInProgress);
369 }
@@ -398,6 +400,7 @@ module.exports = function(
400 const updateQueue = workInProgress.updateQueue;
401 if (updateQueue !== null) {
402 instance.state = beginUpdateQueue(
403 + current,
404 workInProgress,
405 updateQueue,
406 instance,
@@ -414,108 +417,108 @@ module.exports = function(
417
418 // Called on a preexisting class instance. Returns false if a resumed render
419 // could be reused.
417 - function resumeMountClassInstance(
418 - workInProgress: Fiber,
419 - priorityLevel: PriorityLevel,
420 - ): boolean {
421 - const instance = workInProgress.stateNode;
422 - resetInputPointers(workInProgress, instance);
423 -
424 - let newState = workInProgress.memoizedState;
425 - let newProps = workInProgress.pendingProps;
426 - if (!newProps) {
427 - // If there isn't any new props, then we'll reuse the memoized props.
428 - // This could be from already completed work.
429 - newProps = workInProgress.memoizedProps;
430 - invariant(
431 - newProps != null,
432 - 'There should always be pending or memoized props. This error is ' +
433 - 'likely caused by a bug in React. Please file an issue.',
434 - );
435 - }
436 - const newUnmaskedContext = getUnmaskedContext(workInProgress);
437 - const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
438 -
439 - const oldContext = instance.context;
440 - const oldProps = workInProgress.memoizedProps;
441 -
442 - if (
443 - typeof instance.componentWillReceiveProps === 'function' &&
444 - (oldProps !== newProps || oldContext !== newContext)
445 - ) {
446 - callComponentWillReceiveProps(
447 - workInProgress,
448 - instance,
449 - newProps,
450 - newContext,
451 - );
452 - }
453 -
454 - // Process the update queue before calling shouldComponentUpdate
455 - const updateQueue = workInProgress.updateQueue;
456 - if (updateQueue !== null) {
457 - newState = beginUpdateQueue(
458 - workInProgress,
459 - updateQueue,
460 - instance,
461 - newState,
462 - newProps,
463 - priorityLevel,
464 - );
465 - }
466 -
467 - // TODO: Should we deal with a setState that happened after the last
468 - // componentWillMount and before this componentWillMount? Probably
469 - // unsupported anyway.
470 -
471 - if (
472 - !checkShouldComponentUpdate(
473 - workInProgress,
474 - workInProgress.memoizedProps,
475 - newProps,
476 - workInProgress.memoizedState,
477 - newState,
478 - newContext,
479 - )
480 - ) {
481 - // Update the existing instance's state, props, and context pointers even
482 - // though we're bailing out.
483 - instance.props = newProps;
484 - instance.state = newState;
485 - instance.context = newContext;
486 - return false;
487 - }
488 -
489 - // Update the input pointers now so that they are correct when we call
490 - // componentWillMount
491 - instance.props = newProps;
492 - instance.state = newState;
493 - instance.context = newContext;
494 -
495 - if (typeof instance.componentWillMount === 'function') {
496 - callComponentWillMount(workInProgress, instance);
497 - // componentWillMount may have called setState. Process the update queue.
498 - const newUpdateQueue = workInProgress.updateQueue;
499 - if (newUpdateQueue !== null) {
500 - newState = beginUpdateQueue(
501 - workInProgress,
502 - newUpdateQueue,
503 - instance,
504 - newState,
505 - newProps,
506 - priorityLevel,
507 - );
508 - }
509 - }
510 -
511 - if (typeof instance.componentDidMount === 'function') {
512 - workInProgress.effectTag |= Update;
513 - }
514 -
515 - instance.state = newState;
516 -
517 - return true;
518 - }
420 + // function resumeMountClassInstance(
421 + // workInProgress: Fiber,
422 + // priorityLevel: PriorityLevel,
423 + // ): boolean {
424 + // const instance = workInProgress.stateNode;
425 + // resetInputPointers(workInProgress, instance);
426 +
427 + // let newState = workInProgress.memoizedState;
428 + // let newProps = workInProgress.pendingProps;
429 + // if (!newProps) {
430 + // // If there isn't any new props, then we'll reuse the memoized props.
431 + // // This could be from already completed work.
432 + // newProps = workInProgress.memoizedProps;
433 + // invariant(
434 + // newProps != null,
435 + // 'There should always be pending or memoized props. This error is ' +
436 + // 'likely caused by a bug in React. Please file an issue.',
437 + // );
438 + // }
439 + // const newUnmaskedContext = getUnmaskedContext(workInProgress);
440 + // const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
441 +
442 + // const oldContext = instance.context;
443 + // const oldProps = workInProgress.memoizedProps;
444 +
445 + // if (
446 + // typeof instance.componentWillReceiveProps === 'function' &&
447 + // (oldProps !== newProps || oldContext !== newContext)
448 + // ) {
449 + // callComponentWillReceiveProps(
450 + // workInProgress,
451 + // instance,
452 + // newProps,
453 + // newContext,
454 + // );
455 + // }
456 +
457 + // // Process the update queue before calling shouldComponentUpdate
458 + // const updateQueue = workInProgress.updateQueue;
459 + // if (updateQueue !== null) {
460 + // newState = beginUpdateQueue(
461 + // workInProgress,
462 + // updateQueue,
463 + // instance,
464 + // newState,
465 + // newProps,
466 + // priorityLevel,
467 + // );
468 + // }
469 +
470 + // // TODO: Should we deal with a setState that happened after the last
471 + // // componentWillMount and before this componentWillMount? Probably
472 + // // unsupported anyway.
473 +
474 + // if (
475 + // !checkShouldComponentUpdate(
476 + // workInProgress,
477 + // workInProgress.memoizedProps,
478 + // newProps,
479 + // workInProgress.memoizedState,
480 + // newState,
481 + // newContext,
482 + // )
483 + // ) {
484 + // // Update the existing instance's state, props, and context pointers even
485 + // // though we're bailing out.
486 + // instance.props = newProps;
487 + // instance.state = newState;
488 + // instance.context = newContext;
489 + // return false;
490 + // }
491 +
492 + // // Update the input pointers now so that they are correct when we call
493 + // // componentWillMount
494 + // instance.props = newProps;
495 + // instance.state = newState;
496 + // instance.context = newContext;
497 +
498 + // if (typeof instance.componentWillMount === 'function') {
499 + // callComponentWillMount(workInProgress, instance);
500 + // // componentWillMount may have called setState. Process the update queue.
501 + // const newUpdateQueue = workInProgress.updateQueue;
502 + // if (newUpdateQueue !== null) {
503 + // newState = beginUpdateQueue(
504 + // workInProgress,
505 + // newUpdateQueue,
506 + // instance,
507 + // newState,
508 + // newProps,
509 + // priorityLevel,
510 + // );
511 + // }
512 + // }
513 +
514 + // if (typeof instance.componentDidMount === 'function') {
515 + // workInProgress.effectTag |= Update;
516 + // }
517 +
518 + // instance.state = newState;
519 +
520 + // return true;
521 + // }
522
523 // Invokes the update life-cycles and returns false if it shouldn't rerender.
524 function updateClassInstance(
@@ -559,14 +562,14 @@ module.exports = function(
562 }
563
564 // Compute the next state using the memoized state and the update queue.
562 - const updateQueue = workInProgress.updateQueue;
565 const oldState = workInProgress.memoizedState;
566 // TODO: Previous state can be null.
567 let newState;
566 - if (updateQueue !== null) {
568 + if (workInProgress.updateQueue !== null) {
569 newState = beginUpdateQueue(
570 + current,
571 workInProgress,
569 - updateQueue,
572 + workInProgress.updateQueue,
573 instance,
574 oldState,
575 newProps,
@@ -580,7 +583,8 @@ module.exports = function(
583 oldProps === newProps &&
584 oldState === newState &&
585 !hasContextChanged() &&
583 - !(updateQueue !== null && updateQueue.hasForceUpdate)
586 + !(workInProgress.updateQueue !== null &&
587 + workInProgress.updateQueue.hasForceUpdate)
588 ) {
589 // If an update was already in progress, we should schedule an Update
590 // effect even though we're bailing out, so that cWU/cDU are called.
@@ -648,7 +652,7 @@ module.exports = function(
652 adoptClassInstance,
653 constructClassInstance,
654 mountClassInstance,
651 - resumeMountClassInstance,
655 + // resumeMountClassInstance,
656 updateClassInstance,
657 };
658 };
src/renderers/shared/fiber/ReactFiberCompleteWork.js
-13
@@ -70,18 +70,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
70 popHydrationState,
71 } = hydrationContext;
72
73 - function markChildAsProgressed(current, workInProgress, priorityLevel) {
74 - // We now have clones. Let's store them as the currently progressed work.
75 - workInProgress.progressedChild = workInProgress.child;
76 - workInProgress.progressedPriority = priorityLevel;
77 - if (current !== null) {
78 - // We also store it on the current. When the alternate swaps in we can
79 - // continue from this point.
80 - current.progressedChild = workInProgress.progressedChild;
81 - current.progressedPriority = workInProgress.progressedPriority;
82 - }
83 - }
84 -
73 function markUpdate(workInProgress: Fiber) {
74 // Tag the fiber with an update effect. This turns a Placement into
75 // an UpdateAndPlacement.
@@ -159,7 +147,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
147 nextChildren,
148 priority,
149 );
162 - markChildAsProgressed(current, workInProgress, priority);
150 return workInProgress.child;
151 }
152
src/renderers/shared/fiber/ReactFiberScheduler.js
+9 -21
@@ -48,7 +48,7 @@ var ReactFiberHydrationContext = require('ReactFiberHydrationContext');
48 var {ReactCurrentOwner} = require('ReactGlobalSharedState');
49 var getComponentName = require('getComponentName');
50
51 -var {cloneFiber} = require('ReactFiber');
51 +var {createWorkInProgress} = require('ReactFiber');
52 var {onCommitRoot} = require('ReactFiberDevToolsHook');
53
54 var {
@@ -81,7 +81,7 @@ var {
81 ClassComponent,
82 } = require('ReactTypeOfWork');
83
84 -var {getPendingPriority} = require('ReactFiberUpdateQueue');
84 +var {getUpdatePriority} = require('ReactFiberUpdateQueue');
85
86 var {resetContext} = require('ReactFiberContext');
87
@@ -292,7 +292,10 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
292 // unfortunately this is it.
293 resetContextStack();
294
295 - return cloneFiber(highestPriorityRoot.current, highestPriorityLevel);
295 + return createWorkInProgress(
296 + highestPriorityRoot.current,
297 + highestPriorityLevel,
298 + );
299 }
300
301 nextPriorityLevel = NoWork;
@@ -549,27 +552,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
552 }
553
554 function resetWorkPriority(workInProgress: Fiber) {
552 - let newPriority = NoWork;
553 -
554 - // Check for pending update priority. This is usually null so it shouldn't
555 - // be a perf issue.
556 - const queue = workInProgress.updateQueue;
557 - const tag = workInProgress.tag;
558 - if (
559 - queue !== null &&
560 - // TODO: Revisit once updateQueue is typed properly to distinguish between
561 - // update payloads for host components and update queues for composites
562 - (tag === ClassComponent || tag === HostRoot)
563 - ) {
564 - newPriority = getPendingPriority(queue);
565 - }
555 + // Check for pending update priority.
556 + let newPriority = getUpdatePriority(workInProgress);
557
558 // TODO: Coroutines need to visit stateNode
559
569 - // progressedChild is going to be the child set with the highest priority.
570 - // Either it is the same as child, or it just bailed out because it choose
571 - // not to do the work.
572 - let child = workInProgress.progressedChild;
560 + let child = workInProgress.child;
561 while (child !== null) {
562 // Ensure that remaining work priority bubbles up.
563 if (
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+91 -80
@@ -23,6 +23,8 @@ const {
23 TaskPriority,
24 } = require('ReactPriorityLevel');
25
26 +const {ClassComponent, HostRoot} = require('ReactTypeOfWork');
27 +
28 const invariant = require('fbjs/lib/invariant');
29 if (__DEV__) {
30 var warning = require('fbjs/lib/warning');
@@ -85,65 +87,19 @@ function comparePriority(a: PriorityLevel, b: PriorityLevel): number {
87 return a - b;
88 }
89
88 -// Ensures that a fiber has an update queue, creating a new one if needed.
89 -// Returns the new or existing queue.
90 -function ensureUpdateQueue(fiber: Fiber): UpdateQueue {
91 - if (fiber.updateQueue !== null) {
92 - // We already have an update queue.
93 - return fiber.updateQueue;
94 - }
95 -
96 - let queue;
90 +function createUpdateQueue(): UpdateQueue {
91 + const queue: UpdateQueue = {
92 + first: null,
93 + last: null,
94 + hasForceUpdate: false,
95 + callbackList: null,
96 + };
97 if (__DEV__) {
98 - queue = {
99 - first: null,
100 - last: null,
101 - hasForceUpdate: false,
102 - callbackList: null,
103 - isProcessing: false,
104 - };
105 - } else {
106 - queue = {
107 - first: null,
108 - last: null,
109 - hasForceUpdate: false,
110 - callbackList: null,
111 - };
98 + queue.isProcessing = false;
99 }
113 -
114 - fiber.updateQueue = queue;
100 return queue;
101 }
102
118 -// Clones an update queue from a source fiber onto its alternate.
119 -function cloneUpdateQueue(
120 - current: Fiber,
121 - workInProgress: Fiber,
122 -): UpdateQueue | null {
123 - const currentQueue = current.updateQueue;
124 - if (currentQueue === null) {
125 - // The source fiber does not have an update queue.
126 - workInProgress.updateQueue = null;
127 - return null;
128 - }
129 - // If the alternate already has a queue, reuse the previous object.
130 - const altQueue = workInProgress.updateQueue !== null
131 - ? workInProgress.updateQueue
132 - : {};
133 - altQueue.first = currentQueue.first;
134 - altQueue.last = currentQueue.last;
135 -
136 - // These fields are invalid by the time we clone from current. Reset them.
137 - altQueue.hasForceUpdate = false;
138 - altQueue.callbackList = null;
139 - altQueue.isProcessing = false;
140 -
141 - workInProgress.updateQueue = altQueue;
142 -
143 - return altQueue;
144 -}
145 -exports.cloneUpdateQueue = cloneUpdateQueue;
146 -
103 function cloneUpdate(update: Update): Update {
104 return {
105 priorityLevel: update.priorityLevel,
@@ -204,6 +160,32 @@ function findInsertionPosition(queue, update): Update | null {
160 return insertAfter;
161 }
162
163 +function ensureUpdateQueues(fiber: Fiber): [UpdateQueue, UpdateQueue | null] {
164 + const alternateFiber = fiber.alternate;
165 +
166 + let queue1 = fiber.updateQueue;
167 + if (queue1 === null) {
168 + queue1 = fiber.updateQueue = createUpdateQueue();
169 + }
170 +
171 + let queue2;
172 + if (alternateFiber !== null) {
173 + queue2 = alternateFiber.updateQueue;
174 + if (queue2 === null) {
175 + queue2 = alternateFiber.updateQueue = createUpdateQueue();
176 + }
177 + } else {
178 + queue2 = null;
179 + }
180 +
181 + // TODO: Refactor to avoid returning a tuple.
182 + return [
183 + queue1,
184 + // Return null if there is no alternate queue, or if its queue is the same.
185 + queue2 !== queue1 ? queue2 : null,
186 + ];
187 +}
188 +
189 // The work-in-progress queue is a subset of the current queue (if it exists).
190 // We need to insert the incoming update into both lists. However, it's possible
191 // that the correct position in one list will be different from the position in
@@ -234,10 +216,8 @@ function findInsertionPosition(queue, update): Update | null {
216 //
217 // If the update is cloned, it returns the cloned update.
218 function insertUpdate(fiber: Fiber, update: Update): Update | null {
237 - const queue1 = ensureUpdateQueue(fiber);
238 - const queue2 = fiber.alternate !== null
239 - ? ensureUpdateQueue(fiber.alternate)
240 - : null;
219 + // We'll have at least one and at most two distinct update queues.
220 + const [queue1, queue2] = ensureUpdateQueues(fiber);
221
222 // Warn if an update is scheduled from inside an updater function.
223 if (__DEV__) {
@@ -274,13 +254,12 @@ function insertUpdate(fiber: Fiber, update: Update): Update | null {
254 // insertion positions because it mutates the list.
255 insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
256
277 - if (insertBefore1 !== insertBefore2) {
278 - // The insertion positions are different, so we need to clone the update and
279 - // insert the clone into the alternate queue.
280 - const update2 = cloneUpdate(update);
281 - insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
282 - return update2;
283 - } else {
257 + // See if the insertion positions are equal. Be careful to only compare
258 + // non-null values.
259 + if (
260 + (insertBefore1 === insertBefore2 && insertBefore1 !== null) ||
261 + (insertAfter1 === insertAfter2 && insertAfter1 !== null)
262 + ) {
263 // The insertion positions are the same, so when we inserted into the first
264 // queue, it also inserted into the alternate. All we need to do is update
265 // the alternate queue's `first` and `last` pointers, in case they
@@ -291,9 +270,14 @@ function insertUpdate(fiber: Fiber, update: Update): Update | null {
270 if (insertBefore2 === null) {
271 queue2.last = null;
272 }
273 + return null;
274 + } else {
275 + // The insertion positions are different, so we need to clone the update and
276 + // insert the clone into the alternate queue.
277 + const update2 = cloneUpdate(update);
278 + insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
279 + return update2;
280 }
295 -
296 - return null;
281 }
282
283 function addUpdate(
@@ -352,10 +336,17 @@ function addForceUpdate(
336 }
337 exports.addForceUpdate = addForceUpdate;
338
355 -function getPendingPriority(queue: UpdateQueue): PriorityLevel {
356 - return queue.first !== null ? queue.first.priorityLevel : NoWork;
339 +function getUpdatePriority(fiber: Fiber): PriorityLevel {
340 + const updateQueue = fiber.updateQueue;
341 + if (updateQueue === null) {
342 + return NoWork;
343 + }
344 + if (fiber.tag !== ClassComponent && fiber.tag !== HostRoot) {
345 + return NoWork;
346 + }
347 + return updateQueue.first !== null ? updateQueue.first.priorityLevel : NoWork;
348 }
358 -exports.getPendingPriority = getPendingPriority;
349 +exports.getUpdatePriority = getUpdatePriority;
350
351 function addTopLevelUpdate(
352 fiber: Fiber,
@@ -377,13 +368,12 @@ function addTopLevelUpdate(
368 const update2 = insertUpdate(fiber, update);
369
370 if (isTopLevelUnmount) {
371 + // TODO: Redesign the top-level mount/update/unmount API to avoid this
372 + // special case.
373 + const [queue1, queue2] = ensureUpdateQueues(fiber);
374 +
375 // Drop all updates that are lower-priority, so that the tree is not
376 // remounted. We need to do this for both queues.
382 - const queue1 = fiber.updateQueue;
383 - const queue2 = fiber.alternate !== null
384 - ? fiber.alternate.updateQueue
385 - : null;
386 -
377 if (queue1 !== null && update.next !== null) {
378 update.next = null;
379 queue1.last = update;
@@ -407,6 +397,7 @@ function getStateFromUpdate(update, instance, prevState, props) {
397 }
398
399 function beginUpdateQueue(
400 + current: Fiber | null,
401 workInProgress: Fiber,
402 queue: UpdateQueue,
403 instance: any,
@@ -414,19 +405,33 @@ function beginUpdateQueue(
405 props: any,
406 priorityLevel: PriorityLevel,
407 ): any {
408 + if (current !== null && current.updateQueue === queue) {
409 + // We need to create a work-in-progress queue, by cloning the current queue.
410 + const currentQueue = queue;
411 + queue = workInProgress.updateQueue = {
412 + first: currentQueue.first,
413 + last: currentQueue.last,
414 + // These fields are no longer valid because they were already committed.
415 + // Reset them.
416 + callbackList: null,
417 + hasForceUpdate: false,
418 + };
419 + }
420 +
421 if (__DEV__) {
422 // Set this flag so we can warn if setState is called inside the update
423 // function of another setState.
424 queue.isProcessing = true;
425 }
426
423 - queue.hasForceUpdate = false;
427 + // Calculate these using the the existing values as a base.
428 + let callbackList = queue.callbackList;
429 + let hasForceUpdate = queue.hasForceUpdate;
430
431 // Applies updates with matching priority to the previous state to create
432 // a new state object.
433 let state = prevState;
434 let dontMutatePrevState = true;
429 - let callbackList = queue.callbackList;
435 let update = queue.first;
436 while (
437 update !== null &&
@@ -456,7 +461,7 @@ function beginUpdateQueue(
461 }
462 }
463 if (update.isForced) {
459 - queue.hasForceUpdate = true;
464 + hasForceUpdate = true;
465 }
466 // Second condition ignores top-level unmount callbacks if they are not the
467 // last update in the queue, since a subsequent update will cause a remount.
@@ -464,7 +469,7 @@ function beginUpdateQueue(
469 update.callback !== null &&
470 !(update.isTopLevelUnmount && update.next !== null)
471 ) {
467 - callbackList = callbackList || [];
472 + callbackList = callbackList !== null ? callbackList : [];
473 callbackList.push(update.callback);
474 workInProgress.effectTag |= CallbackEffect;
475 }
@@ -472,13 +477,15 @@ function beginUpdateQueue(
477 }
478
479 queue.callbackList = callbackList;
480 + queue.hasForceUpdate = hasForceUpdate;
481
476 - if (queue.first === null && callbackList === null && !queue.hasForceUpdate) {
482 + if (queue.first === null && callbackList === null && !hasForceUpdate) {
483 // The queue is empty and there are no callbacks. We can reset it.
484 workInProgress.updateQueue = null;
485 }
486
487 if (__DEV__) {
488 + // No longer processing.
489 queue.isProcessing = false;
490 }
491
@@ -495,6 +502,10 @@ function commitCallbacks(
502 if (callbackList === null) {
503 return;
504 }
505 +
506 + // Set the list to null to make sure they don't get called more than once.
507 + queue.callbackList = null;
508 +
509 for (let i = 0; i < callbackList.length; i++) {
510 const callback = callbackList[i];
511 invariant(