Move memoization to begin phase
Currently we update the memoized inputs (props, state) during the complete phase, as we go back up the tree. That means we can't reuse work until of its children have completed. By moving memoization to the begin phase, we can do a shallow bailout, reusing a unit of work even if there's still work to do in its children. Memoization now happens whenever a fiber's `child` property is updated; typically, right after reconciling. It's also updated when `shouldComponentUpdate` returns false, because that indicates that the given state and props are equal to the memoized state and props.
Andrew Clark committed
Dec 29, 2016 at 18:09 UTC
1ed304fa877d2694597659d6d537c3d44dac322e
8 files changed
+180
-86
scripts/fiber/tests-failing.txt
-3
@@ -1,6 +1,3 @@
1
-src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js
2
-* does not do a deep comparison
3
-
1
src/addons/__tests__/ReactFragment-test.js
2
* should throw if a plain object is used as a child
3
* should throw if a plain object even if it is in an owner
scripts/fiber/tests-passing.txt
+2
@@ -36,6 +36,7 @@ scripts/error-codes/__tests__/invertObject-test.js
36
37
src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js
38
* provides a default shouldComponentUpdate implementation
39
+* does not do a deep comparison
40
41
src/addons/__tests__/ReactFragment-test.js
42
* warns for numeric keys on objects as children
@@ -1143,6 +1144,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
1144
* can resume work in a bailed subtree within one pass
1145
* can reuse work done after being preempted
1146
* can reuse work if shouldComponentUpdate is false, after being preempted
1147
+* memoizes work even if shouldComponentUpdate returns false
1148
* can update in the middle of a tree using setState
1149
* can queue multiple state updates
1150
* can use updater form of setState
src/renderers/shared/fiber/ReactFiberBeginWork.js
+69
-32
@@ -55,7 +55,6 @@ var {
55
OffscreenPriority,
56
} = require('ReactPriorityLevel');
57
var {
58
- Update,
58
Placement,
59
ContentReset,
60
Err,
@@ -91,7 +90,12 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
90
mountClassInstance,
91
resumeMountClassInstance,
92
updateClassInstance,
94
- } = ReactFiberClassComponent(scheduleUpdate, getPriorityContext);
93
+ } = ReactFiberClassComponent(
94
+ scheduleUpdate,
95
+ getPriorityContext,
96
+ memoizeProps,
97
+ memoizeState,
98
+ );
99
100
function markChildAsProgressed(current, workInProgress, priorityLevel) {
101
// We now have clones. Let's store them as the currently progressed work.
@@ -176,12 +180,13 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
180
// Normally we can bail out on props equality but if context has changed
181
// we don't do the bailout and we have to reuse existing props instead.
182
if (nextChildren === null) {
179
- nextChildren = current && current.memoizedProps;
183
+ nextChildren = workInProgress.memoizedProps;
184
}
185
} else if (nextChildren === null || workInProgress.memoizedProps === nextChildren) {
186
return bailoutOnAlreadyFinishedWork(current, workInProgress);
187
}
188
reconcileChildren(current, workInProgress, nextChildren);
189
+ memoizeProps(workInProgress, nextChildren);
190
return workInProgress.child;
191
}
192
@@ -202,16 +207,20 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
207
// Normally we can bail out on props equality but if context has changed
208
// we don't do the bailout and we have to reuse existing props instead.
209
if (nextProps === null) {
205
- nextProps = current && current.memoizedProps;
210
+ nextProps = memoizedProps;
211
+ }
212
+ } else {
213
+ if (nextProps == null || memoizedProps === nextProps) {
214
+ return bailoutOnAlreadyFinishedWork(current, workInProgress);
215
+ }
216
+ // TODO: Disable this before release, since it is not part of the public API
217
+ // I use this for testing to compare the relative overhead of classes.
218
+ if (typeof fn.shouldComponentUpdate === 'function' &&
219
+ !fn.shouldComponentUpdate(memoizedProps, nextProps)) {
220
+ // Memoize props even if shouldComponentUpdate returns false
221
+ memoizeProps(workInProgress, nextProps);
222
+ return bailoutOnAlreadyFinishedWork(current, workInProgress);
223
}
207
- } else if (nextProps === null || memoizedProps === nextProps || (
208
- // TODO: Disable this before release, since it is not part of the public API
209
- // I use this for testing to compare the relative overhead of classes.
210
- memoizedProps !== null &&
211
- typeof fn.shouldComponentUpdate === 'function' &&
212
- !fn.shouldComponentUpdate(memoizedProps, nextProps)
213
- )) {
214
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
224
}
225
226
var unmaskedContext = getUnmaskedContext(workInProgress);
@@ -226,6 +235,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
235
nextChildren = fn(nextProps, context);
236
}
237
reconcileChildren(current, workInProgress, nextChildren);
238
+ memoizeProps(workInProgress, nextProps);
239
return workInProgress.child;
240
}
241
@@ -258,31 +268,23 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
268
shouldUpdate : boolean,
269
hasContext : boolean,
270
) {
261
- // Schedule side-effects
262
-
271
// Refs should update even if shouldComponentUpdate returns false
272
markRef(current, workInProgress);
273
266
- if (shouldUpdate) {
267
- workInProgress.effectTag |= Update;
268
- } else {
269
- // If an update was already in progress, we should schedule an Update
270
- // effect even though we're bailing out, so that cWU/cDU are called.
271
- if (current) {
272
- const instance = current.stateNode;
273
- if (instance.props !== current.memoizedProps ||
274
- instance.state !== current.memoizedState) {
275
- workInProgress.effectTag |= Update;
276
- }
277
- }
274
+ if (!shouldUpdate) {
275
return bailoutOnAlreadyFinishedWork(current, workInProgress);
276
}
277
281
- // Rerender
278
const instance = workInProgress.stateNode;
279
+
280
+ // Rerender
281
ReactCurrentOwner.current = workInProgress;
282
const nextChildren = instance.render();
283
reconcileChildren(current, workInProgress, nextChildren);
284
+ // Memoize props and state using the values we just used to render.
285
+ // TODO: Restructure so we never read values from the instance.
286
+ memoizeState(workInProgress, instance.state);
287
+ memoizeProps(workInProgress, instance.props);
288
289
// The context might have changed so we need to recalculate it.
290
if (hasContext) {
@@ -321,7 +323,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
323
}
324
const element = state.element;
325
reconcileChildren(current, workInProgress, element);
324
- workInProgress.memoizedState = state;
326
+ memoizeState(workInProgress, state);
327
return workInProgress.child;
328
}
329
// If there is no update queue, that's a bailout because the root has no props.
@@ -338,7 +340,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
340
// Normally we can bail out on props equality but if context has changed
341
// we don't do the bailout and we have to reuse existing props instead.
342
if (nextProps === null) {
341
- nextProps = prevProps;
343
+ nextProps = memoizedProps;
344
if (!nextProps) {
345
throw new Error('We should always have pending or current props.');
346
}
@@ -403,6 +405,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
405
406
// Reconcile the children and stash them for later work.
407
reconcileChildrenAtPriority(current, workInProgress, nextChildren, OffscreenPriority);
408
+ memoizeProps(workInProgress, nextProps);
409
workInProgress.child = current ? current.child : null;
410
411
if (!current) {
@@ -422,10 +425,22 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
425
return null;
426
} else {
427
reconcileChildren(current, workInProgress, nextChildren);
428
+ memoizeProps(workInProgress, nextProps);
429
return workInProgress.child;
430
}
431
}
432
433
+ function updateHostText(current, workInProgress) {
434
+ let nextProps = workInProgress.pendingProps;
435
+ if (nextProps === null) {
436
+ nextProps = workInProgress.memoizedProps;
437
+ }
438
+ memoizeProps(workInProgress, nextProps);
439
+ // Nothing to do here. This is terminal. We'll do the completion step
440
+ // immediately after.
441
+ return null;
442
+ }
443
+
444
function mountIndeterminateComponent(current, workInProgress, priorityLevel) {
445
if (current) {
446
throw new Error('An indeterminate component should never have mounted.');
@@ -484,6 +499,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
499
}
500
}
501
reconcileChildren(current, workInProgress, value);
502
+ memoizeProps(workInProgress, props);
503
return workInProgress.child;
504
}
505
}
@@ -503,6 +519,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
519
return bailoutOnAlreadyFinishedWork(current, workInProgress);
520
}
521
reconcileChildren(current, workInProgress, nextCoroutine.children);
522
+ memoizeProps(workInProgress, nextCoroutine);
523
// This doesn't take arbitrary time so we could synchronously just begin
524
// eagerly do the work of workInProgress.child as an optimization.
525
return workInProgress.child;
@@ -537,9 +554,11 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
554
nextChildren,
555
priorityLevel
556
);
557
+ memoizeProps(workInProgress, nextChildren);
558
markChildAsProgressed(current, workInProgress, priorityLevel);
559
} else {
560
reconcileChildren(current, workInProgress, nextChildren);
561
+ memoizeProps(workInProgress, nextChildren);
562
}
563
return workInProgress.child;
564
}
@@ -606,6 +625,18 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
625
return null;
626
}
627
628
+ function memoizeProps(workInProgress : Fiber, nextProps : any) {
629
+ workInProgress.memoizedProps = nextProps;
630
+ // Reset the pending props
631
+ workInProgress.pendingProps = null;
632
+ }
633
+
634
+ function memoizeState(workInProgress : Fiber, nextState : any) {
635
+ workInProgress.memoizedState = nextState;
636
+ // Don't reset the updateQueue, in case there are pending updates. Resetting
637
+ // is handled by beginUpdateQueue.
638
+ }
639
+
640
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
641
if (workInProgress.pendingWorkPriority === NoWork ||
642
workInProgress.pendingWorkPriority > priorityLevel) {
@@ -639,9 +670,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
670
case HostComponent:
671
return updateHostComponent(current, workInProgress);
672
case HostText:
642
- // Nothing to do here. This is terminal. We'll do the completion step
643
- // immediately after.
644
- return null;
673
+ return updateHostText(current, workInProgress);
674
case CoroutineHandlerPhase:
675
// This is a restart. Reset the tag to the initial phase.
676
workInProgress.tag = CoroutineComponent;
@@ -683,6 +712,14 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
712
// Unmount the current children as if the component rendered null
713
const nextChildren = null;
714
reconcileChildren(current, workInProgress, nextChildren);
715
+
716
+ if (workInProgress.tag === ClassComponent) {
717
+ const instance = workInProgress.stateNode;
718
+ workInProgress.memoizedProps = instance.props;
719
+ workInProgress.memoizedState = instance.state;
720
+ workInProgress.pendingProps = null;
721
+ }
722
+
723
return workInProgress.child;
724
}
725
src/renderers/shared/fiber/ReactFiberClassComponent.js
+61
-11
@@ -15,6 +15,9 @@
15
import type { Fiber } from 'ReactFiber';
16
import type { PriorityLevel } from 'ReactPriorityLevel';
17
18
+var {
19
+ Update,
20
+} = require('ReactTypeOfSideEffect');
21
var {
22
cacheContext,
23
getMaskedContext,
@@ -40,6 +43,8 @@ const isArray = Array.isArray;
43
module.exports = function(
44
scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
45
getPriorityContext : () => PriorityLevel,
46
+ memoizeProps: (workInProgress : Fiber, props : any) => void,
47
+ memoizeState: (workInProgress : Fiber, state : any) => void,
48
) {
49
50
// Class component state updater
@@ -65,7 +70,7 @@ module.exports = function(
70
},
71
};
72
68
- function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState, newContext) {
73
+ function checkShouldComponentUpdate(workInProgress, oldProps, newProps, oldState, newState, newContext) {
74
if (oldProps === null || (workInProgress.updateQueue && workInProgress.updateQueue.hasForceUpdate)) {
75
// If the workInProgress already has an Update effect, return true
76
return true;
@@ -91,7 +96,7 @@ module.exports = function(
96
if (type.prototype && type.prototype.isPureReactComponent) {
97
return (
98
!shallowEqual(oldProps, newProps) ||
94
- !shallowEqual(instance.state, newState)
99
+ !shallowEqual(oldState, newState)
100
);
101
}
102
@@ -198,6 +203,27 @@ module.exports = function(
203
}
204
}
205
206
+
207
+ function markUpdate(workInProgress) {
208
+ workInProgress.effectTag |= Update;
209
+ }
210
+
211
+ function markUpdateIfAlreadyInProgress(current: ?Fiber, workInProgress : Fiber) {
212
+ // If an update was already in progress, we should schedule an Update
213
+ // effect even though we're bailing out, so that cWU/cDU are called.
214
+ if (current) {
215
+ if (workInProgress.memoizedProps !== current.memoizedProps ||
216
+ workInProgress.memoizedState !== current.memoizedState) {
217
+ markUpdate(workInProgress);
218
+ }
219
+ }
220
+ }
221
+
222
+ function resetInputPointers(workInProgress : Fiber, instance : any) {
223
+ instance.props = workInProgress.memoizedProps;
224
+ instance.state = workInProgress.memoizedState;
225
+ }
226
+
227
function adoptClassInstance(workInProgress : Fiber, instance : any) : void {
228
instance.updater = updater;
229
workInProgress.stateNode = instance;
@@ -226,6 +252,7 @@ module.exports = function(
252
253
// Invokes the mount life-cycles on a previously never rendered instance.
254
function mountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : void {
255
+ markUpdate(workInProgress);
256
const instance = workInProgress.stateNode;
257
const state = instance.state || null;
258
@@ -261,6 +288,10 @@ module.exports = function(
288
// Called on a preexisting class instance. Returns false if a resumed render
289
// could be reused.
290
function resumeMountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean {
291
+ markUpdate(workInProgress);
292
+ const instance = workInProgress.stateNode;
293
+ resetInputPointers(workInProgress, instance);
294
+
295
let newState = workInProgress.memoizedState;
296
let newProps = workInProgress.pendingProps;
297
if (!newProps) {
@@ -282,9 +313,15 @@ module.exports = function(
313
workInProgress,
314
workInProgress.memoizedProps,
315
newProps,
316
+ workInProgress.memoizedState,
317
newState,
318
newContext
319
)) {
320
+ // Update the existing instance's state, props, and context pointers even
321
+ // though we're bailing out.
322
+ instance.props = newProps;
323
+ instance.state = newState;
324
+ instance.context = newContext;
325
return false;
326
}
327
@@ -318,8 +355,9 @@ module.exports = function(
355
// Invokes the update life-cycles and returns false if it shouldn't rerender.
356
function updateClassInstance(current : Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean {
357
const instance = workInProgress.stateNode;
358
+ resetInputPointers(workInProgress, instance);
359
322
- const oldProps = workInProgress.memoizedProps || current.memoizedProps;
360
+ const oldProps = workInProgress.memoizedProps;
361
let newProps = workInProgress.pendingProps;
362
if (!newProps) {
363
// If there aren't any new props, then we'll reuse the memoized props.
@@ -365,28 +403,40 @@ module.exports = function(
403
oldState === newState &&
404
!hasContextChanged() &&
405
!(updateQueue && updateQueue.hasForceUpdate)) {
406
+ markUpdateIfAlreadyInProgress(current, workInProgress);
407
return false;
408
}
409
371
- if (!checkShouldComponentUpdate(
410
+ const shouldUpdate = checkShouldComponentUpdate(
411
workInProgress,
412
oldProps,
413
newProps,
414
+ oldState,
415
newState,
416
newContext
377
- )) {
378
- // TODO: Should this get the new props/state updated regardless?
379
- return false;
380
- }
417
+ );
418
382
- if (typeof instance.componentWillUpdate === 'function') {
383
- instance.componentWillUpdate(newProps, newState, newContext);
419
+ if (shouldUpdate) {
420
+ markUpdate(workInProgress);
421
+ if (typeof instance.componentWillUpdate === 'function') {
422
+ instance.componentWillUpdate(newProps, newState, newContext);
423
+ }
424
+ } else {
425
+ markUpdateIfAlreadyInProgress(current, workInProgress);
426
+
427
+ // If shouldComponentUpdate returned false, we should still update the
428
+ // memoized props/state to indicate that this work can be reused.
429
+ memoizeProps(workInProgress, newProps);
430
+ memoizeState(workInProgress, newState);
431
}
432
433
+ // Update the existing instance's state, props, and context pointers even
434
+ // if shouldComponentUpdate returns false.
435
instance.props = newProps;
436
instance.state = newState;
437
instance.context = newContext;
389
- return true;
438
+
439
+ return shouldUpdate;
440
}
441
442
return {
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+3
-28
@@ -100,7 +100,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
100
}
101
102
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
103
- var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
103
+ var coroutine = (workInProgress.memoizedProps : ?ReactCoroutine);
104
if (!coroutine) {
105
throw new Error('Should be resolved by now');
106
}
@@ -170,24 +170,14 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
170
171
switch (workInProgress.tag) {
172
case FunctionalComponent:
173
- workInProgress.memoizedProps = workInProgress.pendingProps;
173
return null;
174
case ClassComponent: {
175
// We are leaving this subtree, so pop context if any.
176
popContextProvider(workInProgress);
178
- // Don't use the state queue to compute the memoized state. We already
179
- // merged it and assigned it to the instance. Transfer it from there.
180
- // Also need to transfer the props, because pendingProps will be null
181
- // in the case of an update.
182
- const instance = workInProgress.stateNode;
183
- workInProgress.memoizedState = instance.state;
184
- workInProgress.memoizedProps = instance.props;
185
-
177
return null;
178
}
179
case HostRoot: {
180
// TODO: Pop the host container after #8607 lands.
190
- workInProgress.memoizedProps = workInProgress.pendingProps;
181
const fiberRoot = (workInProgress.stateNode : FiberRoot);
182
if (fiberRoot.pendingContext) {
183
fiberRoot.context = fiberRoot.pendingContext;
@@ -198,7 +188,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
188
case HostComponent:
189
popHostContext(workInProgress);
190
const type = workInProgress.type;
201
- let newProps = workInProgress.pendingProps;
191
+ const newProps = workInProgress.memoizedProps;
192
if (current && workInProgress.stateNode != null) {
193
// If we have an alternate, that means this is an update and we need to
194
// schedule a side-effect to do the updates.
@@ -207,9 +197,6 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
197
// have newProps so we'll have to reuse them.
198
// TODO: Split the update API as separate for the props vs. children.
199
// Even better would be if children weren't special cased at all tho.
210
- if (!newProps) {
211
- newProps = workInProgress.memoizedProps || oldProps;
212
- }
200
const instance : I = workInProgress.stateNode;
201
const currentHostContext = getHostContext();
202
if (prepareUpdate(instance, type, oldProps, newProps, currentHostContext)) {
@@ -255,20 +242,11 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
242
markUpdate(workInProgress);
243
}
244
}
258
- workInProgress.memoizedProps = newProps;
245
return null;
246
case HostText:
261
- let newText = workInProgress.pendingProps;
247
+ let newText = workInProgress.memoizedProps;
248
if (current && workInProgress.stateNode != null) {
249
const oldText = current.memoizedProps;
264
- if (newText === null) {
265
- // If this was a bail out we need to fall back to memoized text.
266
- // This works the same way as HostComponent.
267
- newText = workInProgress.memoizedProps;
268
- if (newText === null) {
269
- newText = oldText;
270
- }
271
- }
250
// If we have an alternate, that means this is an update and we need
251
// to schedule a side-effect to do the updates.
252
if (oldText !== newText) {
@@ -293,7 +271,6 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
271
case CoroutineComponent:
272
return moveCoroutineToHandlerPhase(current, workInProgress);
273
case CoroutineHandlerPhase:
296
- workInProgress.memoizedProps = workInProgress.pendingProps;
274
// Reset the tag to now be a first phase coroutine.
275
workInProgress.tag = CoroutineComponent;
276
return null;
@@ -301,12 +278,10 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
278
// Does nothing.
279
return null;
280
case Fragment:
304
- workInProgress.memoizedProps = workInProgress.pendingProps;
281
return null;
282
case HostPortal:
283
// TODO: Only mark this as an update if we have any pending callbacks.
284
markUpdate(workInProgress);
309
- workInProgress.memoizedProps = workInProgress.pendingProps;
285
popHostContainer(workInProgress);
286
return null;
287
src/renderers/shared/fiber/ReactFiberScheduler.js
-4
@@ -442,10 +442,6 @@ module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, T
442
const current = workInProgress.alternate;
443
const next = completeWork(current, workInProgress);
444
445
- // The work is now done. We don't need this anymore. This flags
446
- // to the system not to redo any work here.
447
- workInProgress.pendingProps = null;
448
-
445
const returnFiber = workInProgress.return;
446
const siblingFiber = workInProgress.sibling;
447
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+43
-6
@@ -628,6 +628,43 @@ describe('ReactIncremental', () => {
628
629
});
630
631
+ it('memoizes work even if shouldComponentUpdate returns false', () => {
632
+ let ops = [];
633
+ class Foo extends React.Component {
634
+ shouldComponentUpdate(nextProps) {
635
+ // this.props is the memoized props. So this should return true for
636
+ // every update except the first one.
637
+ const shouldUpdate = this.props.step !== 1;
638
+ ops.push('shouldComponentUpdate: ' + shouldUpdate);
639
+ return shouldUpdate;
640
+ }
641
+ render() {
642
+ ops.push('render');
643
+ return <div />;
644
+ }
645
+ }
646
+
647
+ ReactNoop.render(<Foo step={1} />);
648
+ ReactNoop.flush();
649
+
650
+ ops = [];
651
+ ReactNoop.render(<Foo step={2} />);
652
+ ReactNoop.flush();
653
+ expect(ops).toEqual([
654
+ 'shouldComponentUpdate: false',
655
+ ]);
656
+
657
+ ops = [];
658
+ ReactNoop.render(<Foo step={3} />);
659
+ ReactNoop.flush();
660
+ expect(ops).toEqual([
661
+ // If the memoized props were not updated during last bail out, sCU will
662
+ // keep returning false.
663
+ 'shouldComponentUpdate: true',
664
+ 'render',
665
+ ]);
666
+ });
667
+
668
it('can update in the middle of a tree using setState', () => {
669
let instance;
670
class Bar extends React.Component {
@@ -873,13 +910,13 @@ describe('ReactIncremental', () => {
910
911
ReactNoop.render(<Foo step={1} />);
912
ReactNoop.flushDeferredPri(50);
876
- // A completed and was reused. B completed but couldn't be reused because
877
- // props differences. C didn't complete and therefore couldn't be reused.
878
- // D never even started so it needed a new instance.
879
- expect(ops).toEqual(['Foo', 'Bar:B2', 'Bar:C', 'Bar:D']);
913
+ // A was memoized and reused. B was memoized but couldn't be reused because
914
+ // props differences. C was memoized and reused. D never even started so it
915
+ // needed a new instance.
916
+ expect(ops).toEqual(['Foo', 'Bar:B2', 'Bar:D']);
917
918
// We expect each rerender to correspond to a new instance.
882
- expect(instances.size).toBe(6);
919
+ expect(instances.size).toBe(5);
920
});
921
922
it('gets new props when setting state on a partly updated component', () => {
@@ -935,7 +972,7 @@ describe('ReactIncremental', () => {
972
ops = [];
973
974
ReactNoop.flush();
938
- expect(ops).toEqual(['Bar:A-1', 'Baz', 'Baz']);
975
+ expect(ops).toEqual(['Bar:A-1', 'Baz']);
976
});
977
978
it('calls componentWillMount twice if the initial render is aborted', () => {
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+2
-2
@@ -667,7 +667,7 @@ describe('ReactIncrementalSideEffects', () => {
667
),
668
]);
669
670
- expect(ops).toEqual(['Foo', 'Baz', 'Bar']);
670
+ expect(ops).toEqual(['Foo']);
671
ops = [];
672
673
ReactNoop.flush();
@@ -861,7 +861,7 @@ describe('ReactIncrementalSideEffects', () => {
861
),
862
]);
863
864
- expect(ops).toEqual(['Bar', 'Bar']);
864
+ expect(ops).toEqual(['Bar']);
865
});
866
// TODO: Test that side-effects are not cut off when a work in progress node
867
// moves to "current" without flushing due to having lower priority. Does this