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(