Skip to content

Commit 199db63

Browse files
authored
Merge pull request #8655 from acdlite/fibermemoizepremptedwork
[Fiber] Move memoization to begin phase
2 parents d28ac9e + 54ebcbc commit 199db63

9 files changed

Lines changed: 289 additions & 87 deletions

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js
2-
* does not do a deep comparison
3-
41
src/addons/__tests__/ReactFragment-test.js
52
* should throw if a plain object is used as a child
63
* should throw if a plain object even if it is in an owner

scripts/fiber/tests-passing.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ scripts/error-codes/__tests__/invertObject-test.js
3636

3737
src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js
3838
* provides a default shouldComponentUpdate implementation
39+
* does not do a deep comparison
3940

4041
src/addons/__tests__/ReactFragment-test.js
4142
* warns for numeric keys on objects as children
@@ -1144,7 +1145,9 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
11441145
* can resume work in a subtree even when a parent bails out
11451146
* can resume work in a bailed subtree within one pass
11461147
* can reuse work done after being preempted
1148+
* can reuse work that began but did not complete, after being preempted
11471149
* can reuse work if shouldComponentUpdate is false, after being preempted
1150+
* memoizes work even if shouldComponentUpdate returns false
11481151
* can update in the middle of a tree using setState
11491152
* can queue multiple state updates
11501153
* can use updater form of setState
@@ -1409,6 +1412,7 @@ src/renderers/shared/shared/__tests__/ReactCompositeComponentState-test.js
14091412
* should call componentDidUpdate of children first
14101413
* should batch unmounts
14111414
* should update state when called from child cWRP
1415+
* should merge state when sCU returns false
14121416

14131417
src/renderers/shared/shared/__tests__/ReactEmptyComponent-test.js
14141418
* should not produce child DOM nodes for null and false

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ var {
5555
OffscreenPriority,
5656
} = require('ReactPriorityLevel');
5757
var {
58-
Update,
5958
Placement,
6059
ContentReset,
6160
Err,
@@ -91,7 +90,12 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
9190
mountClassInstance,
9291
resumeMountClassInstance,
9392
updateClassInstance,
94-
} = ReactFiberClassComponent(scheduleUpdate, getPriorityContext);
93+
} = ReactFiberClassComponent(
94+
scheduleUpdate,
95+
getPriorityContext,
96+
memoizeProps,
97+
memoizeState,
98+
);
9599

96100
function markChildAsProgressed(current, workInProgress, priorityLevel) {
97101
// 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>(
176180
// Normally we can bail out on props equality but if context has changed
177181
// we don't do the bailout and we have to reuse existing props instead.
178182
if (nextChildren === null) {
179-
nextChildren = current && current.memoizedProps;
183+
nextChildren = workInProgress.memoizedProps;
180184
}
181185
} else if (nextChildren === null || workInProgress.memoizedProps === nextChildren) {
182186
return bailoutOnAlreadyFinishedWork(current, workInProgress);
183187
}
184188
reconcileChildren(current, workInProgress, nextChildren);
189+
memoizeProps(workInProgress, nextChildren);
185190
return workInProgress.child;
186191
}
187192

@@ -202,16 +207,20 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
202207
// Normally we can bail out on props equality but if context has changed
203208
// we don't do the bailout and we have to reuse existing props instead.
204209
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);
206223
}
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);
215224
}
216225

217226
var unmaskedContext = getUnmaskedContext(workInProgress);
@@ -226,6 +235,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
226235
nextChildren = fn(nextProps, context);
227236
}
228237
reconcileChildren(current, workInProgress, nextChildren);
238+
memoizeProps(workInProgress, nextProps);
229239
return workInProgress.child;
230240
}
231241

@@ -258,31 +268,23 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
258268
shouldUpdate : boolean,
259269
hasContext : boolean,
260270
) {
261-
// Schedule side-effects
262-
263271
// Refs should update even if shouldComponentUpdate returns false
264272
markRef(current, workInProgress);
265273

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) {
278275
return bailoutOnAlreadyFinishedWork(current, workInProgress);
279276
}
280277

281-
// Rerender
282278
const instance = workInProgress.stateNode;
279+
280+
// Rerender
283281
ReactCurrentOwner.current = workInProgress;
284282
const nextChildren = instance.render();
285283
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);
286288

287289
// The context might have changed so we need to recalculate it.
288290
if (hasContext) {
@@ -321,7 +323,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
321323
}
322324
const element = state.element;
323325
reconcileChildren(current, workInProgress, element);
324-
workInProgress.memoizedState = state;
326+
memoizeState(workInProgress, state);
325327
return workInProgress.child;
326328
}
327329
// 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>(
338340
// Normally we can bail out on props equality but if context has changed
339341
// we don't do the bailout and we have to reuse existing props instead.
340342
if (nextProps === null) {
341-
nextProps = prevProps;
343+
nextProps = memoizedProps;
342344
if (!nextProps) {
343345
throw new Error('We should always have pending or current props.');
344346
}
@@ -403,6 +405,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
403405

404406
// Reconcile the children and stash them for later work.
405407
reconcileChildrenAtPriority(current, workInProgress, nextChildren, OffscreenPriority);
408+
memoizeProps(workInProgress, nextProps);
406409
workInProgress.child = current ? current.child : null;
407410

408411
if (!current) {
@@ -422,10 +425,22 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
422425
return null;
423426
} else {
424427
reconcileChildren(current, workInProgress, nextChildren);
428+
memoizeProps(workInProgress, nextProps);
425429
return workInProgress.child;
426430
}
427431
}
428432

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+
429444
function mountIndeterminateComponent(current, workInProgress, priorityLevel) {
430445
if (current) {
431446
throw new Error('An indeterminate component should never have mounted.');
@@ -484,6 +499,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
484499
}
485500
}
486501
reconcileChildren(current, workInProgress, value);
502+
memoizeProps(workInProgress, props);
487503
return workInProgress.child;
488504
}
489505
}
@@ -503,6 +519,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
503519
return bailoutOnAlreadyFinishedWork(current, workInProgress);
504520
}
505521
reconcileChildren(current, workInProgress, nextCoroutine.children);
522+
memoizeProps(workInProgress, nextCoroutine);
506523
// This doesn't take arbitrary time so we could synchronously just begin
507524
// eagerly do the work of workInProgress.child as an optimization.
508525
return workInProgress.child;
@@ -537,9 +554,11 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
537554
nextChildren,
538555
priorityLevel
539556
);
557+
memoizeProps(workInProgress, nextChildren);
540558
markChildAsProgressed(current, workInProgress, priorityLevel);
541559
} else {
542560
reconcileChildren(current, workInProgress, nextChildren);
561+
memoizeProps(workInProgress, nextChildren);
543562
}
544563
return workInProgress.child;
545564
}
@@ -606,6 +625,18 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
606625
return null;
607626
}
608627

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+
609640
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
610641
if (workInProgress.pendingWorkPriority === NoWork ||
611642
workInProgress.pendingWorkPriority > priorityLevel) {
@@ -639,9 +670,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
639670
case HostComponent:
640671
return updateHostComponent(current, workInProgress);
641672
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);
645674
case CoroutineHandlerPhase:
646675
// This is a restart. Reset the tag to the initial phase.
647676
workInProgress.tag = CoroutineComponent;
@@ -683,6 +712,14 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
683712
// Unmount the current children as if the component rendered null
684713
const nextChildren = null;
685714
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+
686723
return workInProgress.child;
687724
}
688725

0 commit comments

Comments
 (0)