Skip to content

Commit 1bc975d

Browse files
authored
Don't stop context traversal at matching consumers (facebook#13391)
* Don't stop context traversal at matching consumers Originally, the idea was to time slice the traversal. This worked when there was only a single context type per consumer. Now that each fiber may have a list of context dependencies, including duplicate entries, that optimization no longer makes sense – we could end up scanning the same subtree multiple times. * Remove changedBits from context object and stack Don't need it anymore, yay
1 parent 83e446e commit 1bc975d

File tree

5 files changed

+35
-84
lines changed

5 files changed

+35
-84
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ import {
9696
resumeMountClassInstance,
9797
updateClassInstance,
9898
} from './ReactFiberClassComponent';
99-
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
10099

101100
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
102101

@@ -290,10 +289,7 @@ function updateClassComponent(
290289
// During mounting we don't know the child context yet as the instance doesn't exist.
291290
// We will invalidate the child context in finishClassComponent() right after rendering.
292291
const hasContext = pushLegacyContextProvider(workInProgress);
293-
const hasPendingNewContext = prepareToReadContext(
294-
workInProgress,
295-
renderExpirationTime,
296-
);
292+
prepareToReadContext(workInProgress, renderExpirationTime);
297293

298294
let shouldUpdate;
299295
if (current === null) {
@@ -311,15 +307,13 @@ function updateClassComponent(
311307
// In a resume, we'll already have an instance we can reuse.
312308
shouldUpdate = resumeMountClassInstance(
313309
workInProgress,
314-
hasPendingNewContext,
315310
renderExpirationTime,
316311
);
317312
}
318313
} else {
319314
shouldUpdate = updateClassInstance(
320315
current,
321316
workInProgress,
322-
hasPendingNewContext,
323317
renderExpirationTime,
324318
);
325319
}
@@ -841,20 +835,17 @@ function updateContextProvider(
841835
}
842836
}
843837

844-
let changedBits: number;
845-
if (oldProps === null) {
846-
// Initial render
847-
changedBits = MAX_SIGNED_31_BIT_INT;
848-
} else {
838+
pushProvider(workInProgress, newValue);
839+
840+
if (oldProps !== null) {
849841
const oldValue = oldProps.value;
850-
changedBits = calculateChangedBits(context, newValue, oldValue);
842+
const changedBits = calculateChangedBits(context, newValue, oldValue);
851843
if (changedBits === 0) {
852844
// No change. Bailout early if children are the same.
853845
if (
854846
oldProps.children === newProps.children &&
855847
!hasLegacyContextChanged()
856848
) {
857-
pushProvider(workInProgress, 0);
858849
return bailoutOnAlreadyFinishedWork(
859850
current,
860851
workInProgress,
@@ -873,8 +864,6 @@ function updateContextProvider(
873864
}
874865
}
875866

876-
pushProvider(workInProgress, changedBits);
877-
878867
const newChildren = newProps.children;
879868
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
880869
return workInProgress.child;
@@ -1014,9 +1003,11 @@ function beginWork(
10141003
workInProgress.stateNode.containerInfo,
10151004
);
10161005
break;
1017-
case ContextProvider:
1018-
pushProvider(workInProgress, 0);
1006+
case ContextProvider: {
1007+
const newValue = workInProgress.memoizedProps.value;
1008+
pushProvider(workInProgress, newValue);
10191009
break;
1010+
}
10201011
case Profiler:
10211012
if (enableProfilerTimer) {
10221013
workInProgress.effectTag |= Update;

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,6 @@ function mountClassInstance(
744744

745745
function resumeMountClassInstance(
746746
workInProgress: Fiber,
747-
hasPendingNewContext: boolean,
748747
renderExpirationTime: ExpirationTime,
749748
): boolean {
750749
const ctor = workInProgress.type;
@@ -806,7 +805,6 @@ function resumeMountClassInstance(
806805
oldProps === newProps &&
807806
oldState === newState &&
808807
!hasContextChanged() &&
809-
!hasPendingNewContext &&
810808
!checkHasForceUpdateAfterProcessing()
811809
) {
812810
// If an update was already in progress, we should schedule an Update
@@ -828,7 +826,6 @@ function resumeMountClassInstance(
828826

829827
const shouldUpdate =
830828
checkHasForceUpdateAfterProcessing() ||
831-
hasPendingNewContext ||
832829
checkShouldComponentUpdate(
833830
workInProgress,
834831
oldProps,
@@ -884,7 +881,6 @@ function resumeMountClassInstance(
884881
function updateClassInstance(
885882
current: Fiber,
886883
workInProgress: Fiber,
887-
hasPendingNewContext: boolean,
888884
renderExpirationTime: ExpirationTime,
889885
): boolean {
890886
const ctor = workInProgress.type;
@@ -947,7 +943,6 @@ function updateClassInstance(
947943
oldProps === newProps &&
948944
oldState === newState &&
949945
!hasContextChanged() &&
950-
!hasPendingNewContext &&
951946
!checkHasForceUpdateAfterProcessing()
952947
) {
953948
// If an update was already in progress, we should schedule an Update
@@ -982,7 +977,6 @@ function updateClassInstance(
982977

983978
const shouldUpdate =
984979
checkHasForceUpdateAfterProcessing() ||
985-
hasPendingNewContext ||
986980
checkShouldComponentUpdate(
987981
workInProgress,
988982
oldProps,

packages/react-reconciler/src/ReactFiberNewContext.js

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,18 @@ import {isPrimaryRenderer} from './ReactFiberHostConfig';
2323
import {createCursor, push, pop} from './ReactFiberStack';
2424
import maxSigned31BitInt from './maxSigned31BitInt';
2525
import {NoWork} from './ReactFiberExpirationTime';
26-
import {ContextProvider} from 'shared/ReactTypeOfWork';
26+
import {ContextProvider, ClassComponent} from 'shared/ReactTypeOfWork';
2727

2828
import invariant from 'shared/invariant';
2929
import warning from 'shared/warning';
30+
import {
31+
createUpdate,
32+
enqueueUpdate,
33+
ForceUpdate,
34+
} from 'react-reconciler/src/ReactUpdateQueue';
3035

3136
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
3237
const valueCursor: StackCursor<mixed> = createCursor(null);
33-
const changedBitsCursor: StackCursor<number> = createCursor(0);
3438

3539
let rendererSigil;
3640
if (__DEV__) {
@@ -50,15 +54,13 @@ export function resetContextDependences(): void {
5054
lastContextWithAllBitsObserved = null;
5155
}
5256

53-
export function pushProvider(providerFiber: Fiber, changedBits: number): void {
54-
const context: ReactContext<any> = providerFiber.type._context;
57+
export function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
58+
const context: ReactContext<T> = providerFiber.type._context;
5559

5660
if (isPrimaryRenderer) {
57-
push(changedBitsCursor, context._changedBits, providerFiber);
5861
push(valueCursor, context._currentValue, providerFiber);
5962

60-
context._currentValue = providerFiber.pendingProps.value;
61-
context._changedBits = changedBits;
63+
context._currentValue = nextValue;
6264
if (__DEV__) {
6365
warningWithoutStack(
6466
context._currentRenderer === undefined ||
@@ -70,11 +72,9 @@ export function pushProvider(providerFiber: Fiber, changedBits: number): void {
7072
context._currentRenderer = rendererSigil;
7173
}
7274
} else {
73-
push(changedBitsCursor, context._changedBits2, providerFiber);
7475
push(valueCursor, context._currentValue2, providerFiber);
7576

76-
context._currentValue2 = providerFiber.pendingProps.value;
77-
context._changedBits2 = changedBits;
77+
context._currentValue2 = nextValue;
7878
if (__DEV__) {
7979
warningWithoutStack(
8080
context._currentRenderer2 === undefined ||
@@ -89,19 +89,15 @@ export function pushProvider(providerFiber: Fiber, changedBits: number): void {
8989
}
9090

9191
export function popProvider(providerFiber: Fiber): void {
92-
const changedBits = changedBitsCursor.current;
9392
const currentValue = valueCursor.current;
9493

9594
pop(valueCursor, providerFiber);
96-
pop(changedBitsCursor, providerFiber);
9795

9896
const context: ReactContext<any> = providerFiber.type._context;
9997
if (isPrimaryRenderer) {
10098
context._currentValue = currentValue;
101-
context._changedBits = changedBits;
10299
} else {
103100
context._currentValue2 = currentValue;
104-
context._changedBits2 = changedBits;
105101
}
106102
}
107103

@@ -162,6 +158,18 @@ export function propagateContextChange(
162158
(dependency.observedBits & changedBits) !== 0
163159
) {
164160
// Match! Schedule an update on this fiber.
161+
162+
if (fiber.tag === ClassComponent) {
163+
// Schedule a force update on the work-in-progress.
164+
const update = createUpdate(renderExpirationTime);
165+
update.tag = ForceUpdate;
166+
// TODO: Because we don't have a work-in-progress, this will add the
167+
// update to the current fiber, too, which means it will persist even if
168+
// this render is thrown away. Since it's a race condition, not sure it's
169+
// worth fixing.
170+
enqueueUpdate(fiber, update);
171+
}
172+
165173
if (
166174
fiber.expirationTime === NoWork ||
167175
fiber.expirationTime > renderExpirationTime
@@ -206,13 +214,8 @@ export function propagateContextChange(
206214
}
207215
node = node.return;
208216
}
209-
// Don't scan deeper than a matching consumer. When we render the
210-
// consumer, we'll continue scanning from that point. This way the
211-
// scanning work is time-sliced.
212-
nextFiber = null;
213-
} else {
214-
nextFiber = fiber.child;
215217
}
218+
nextFiber = fiber.child;
216219
dependency = dependency.next;
217220
} while (dependency !== null);
218221
} else if (fiber.tag === ContextProvider) {
@@ -253,46 +256,13 @@ export function propagateContextChange(
253256
export function prepareToReadContext(
254257
workInProgress: Fiber,
255258
renderExpirationTime: ExpirationTime,
256-
): boolean {
259+
): void {
257260
currentlyRenderingFiber = workInProgress;
258261
lastContextDependency = null;
259262
lastContextWithAllBitsObserved = null;
260263

261-
const firstContextDependency = workInProgress.firstContextDependency;
262-
if (firstContextDependency !== null) {
263-
// Reset the work-in-progress list
264-
workInProgress.firstContextDependency = null;
265-
266-
// Iterate through the context dependencies and see if there were any
267-
// changes. If so, continue propagating the context change by scanning
268-
// the child subtree.
269-
let dependency = firstContextDependency;
270-
let hasPendingContext = false;
271-
do {
272-
const context = dependency.context;
273-
const changedBits = isPrimaryRenderer
274-
? context._changedBits
275-
: context._changedBits2;
276-
if (changedBits !== 0) {
277-
// Resume context change propagation. We need to call this even if
278-
// this fiber bails out, in case deeply nested consumers observe more
279-
// bits than this one.
280-
propagateContextChange(
281-
workInProgress,
282-
context,
283-
changedBits,
284-
renderExpirationTime,
285-
);
286-
if ((changedBits & dependency.observedBits) !== 0) {
287-
hasPendingContext = true;
288-
}
289-
}
290-
dependency = dependency.next;
291-
} while (dependency !== null);
292-
return hasPendingContext;
293-
} else {
294-
return false;
295-
}
264+
// Reset the work-in-progress list
265+
workInProgress.firstContextDependency = null;
296266
}
297267

298268
export function readContext<T>(

packages/react/src/ReactContext.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ export function createContext<T>(
5757
// Secondary renderers store their context values on separate fields.
5858
_currentValue: defaultValue,
5959
_currentValue2: defaultValue,
60-
_changedBits: 0,
61-
_changedBits2: 0,
6260
// These are circular
6361
Provider: (null: any),
6462
Consumer: (null: any),

packages/shared/ReactTypes.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ export type ReactContext<T> = {
8585

8686
_currentValue: T,
8787
_currentValue2: T,
88-
_changedBits: number,
89-
_changedBits2: number,
9088

9189
// DEV only
9290
_currentRenderer?: Object | null,

0 commit comments

Comments
 (0)