@samitouri / QOS-React-2 / commits / 1bc975d073

Don't stop context traversal at matching consumers (#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

Andrew Clark committed Aug 15, 2018 at 11:19 UTC 1bc975d073dd6f60217d645b43a84e79f96a216b
5 files changed +35 -84
packages/react-reconciler/src/ReactFiberBeginWork.js
+9 -18
@@ -96,7 +96,6 @@ import {
96 resumeMountClassInstance,
97 updateClassInstance,
98 } from './ReactFiberClassComponent';
99 -import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
99
100 const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
101
@@ -290,10 +289,7 @@ function updateClassComponent(
289 // During mounting we don't know the child context yet as the instance doesn't exist.
290 // We will invalidate the child context in finishClassComponent() right after rendering.
291 const hasContext = pushLegacyContextProvider(workInProgress);
293 - const hasPendingNewContext = prepareToReadContext(
294 - workInProgress,
295 - renderExpirationTime,
296 - );
292 + prepareToReadContext(workInProgress, renderExpirationTime);
293
294 let shouldUpdate;
295 if (current === null) {
@@ -311,7 +307,6 @@ function updateClassComponent(
307 // In a resume, we'll already have an instance we can reuse.
308 shouldUpdate = resumeMountClassInstance(
309 workInProgress,
314 - hasPendingNewContext,
310 renderExpirationTime,
311 );
312 }
@@ -319,7 +314,6 @@ function updateClassComponent(
314 shouldUpdate = updateClassInstance(
315 current,
316 workInProgress,
322 - hasPendingNewContext,
317 renderExpirationTime,
318 );
319 }
@@ -841,20 +835,17 @@ function updateContextProvider(
835 }
836 }
837
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) {
841 const oldValue = oldProps.value;
850 - changedBits = calculateChangedBits(context, newValue, oldValue);
842 + const changedBits = calculateChangedBits(context, newValue, oldValue);
843 if (changedBits === 0) {
844 // No change. Bailout early if children are the same.
845 if (
846 oldProps.children === newProps.children &&
847 !hasLegacyContextChanged()
848 ) {
857 - pushProvider(workInProgress, 0);
849 return bailoutOnAlreadyFinishedWork(
850 current,
851 workInProgress,
@@ -873,8 +864,6 @@ function updateContextProvider(
864 }
865 }
866
876 - pushProvider(workInProgress, changedBits);
877 -
867 const newChildren = newProps.children;
868 reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
869 return workInProgress.child;
@@ -1014,9 +1003,11 @@ function beginWork(
1003 workInProgress.stateNode.containerInfo,
1004 );
1005 break;
1017 - case ContextProvider:
1018 - pushProvider(workInProgress, 0);
1006 + case ContextProvider: {
1007 + const newValue = workInProgress.memoizedProps.value;
1008 + pushProvider(workInProgress, newValue);
1009 break;
1010 + }
1011 case Profiler:
1012 if (enableProfilerTimer) {
1013 workInProgress.effectTag |= Update;
packages/react-reconciler/src/ReactFiberClassComponent.js
-6
@@ -744,7 +744,6 @@ function mountClassInstance(
744
745 function resumeMountClassInstance(
746 workInProgress: Fiber,
747 - hasPendingNewContext: boolean,
747 renderExpirationTime: ExpirationTime,
748 ): boolean {
749 const ctor = workInProgress.type;
@@ -806,7 +805,6 @@ function resumeMountClassInstance(
805 oldProps === newProps &&
806 oldState === newState &&
807 !hasContextChanged() &&
809 - !hasPendingNewContext &&
808 !checkHasForceUpdateAfterProcessing()
809 ) {
810 // If an update was already in progress, we should schedule an Update
@@ -828,7 +826,6 @@ function resumeMountClassInstance(
826
827 const shouldUpdate =
828 checkHasForceUpdateAfterProcessing() ||
831 - hasPendingNewContext ||
829 checkShouldComponentUpdate(
830 workInProgress,
831 oldProps,
@@ -884,7 +881,6 @@ function resumeMountClassInstance(
881 function updateClassInstance(
882 current: Fiber,
883 workInProgress: Fiber,
887 - hasPendingNewContext: boolean,
884 renderExpirationTime: ExpirationTime,
885 ): boolean {
886 const ctor = workInProgress.type;
@@ -947,7 +943,6 @@ function updateClassInstance(
943 oldProps === newProps &&
944 oldState === newState &&
945 !hasContextChanged() &&
950 - !hasPendingNewContext &&
946 !checkHasForceUpdateAfterProcessing()
947 ) {
948 // If an update was already in progress, we should schedule an Update
@@ -982,7 +977,6 @@ function updateClassInstance(
977
978 const shouldUpdate =
979 checkHasForceUpdateAfterProcessing() ||
985 - hasPendingNewContext ||
980 checkShouldComponentUpdate(
981 workInProgress,
982 oldProps,
packages/react-reconciler/src/ReactFiberNewContext.js
+26 -56
@@ -23,14 +23,18 @@ import {isPrimaryRenderer} from './ReactFiberHostConfig';
23 import {createCursor, push, pop} from './ReactFiberStack';
24 import maxSigned31BitInt from './maxSigned31BitInt';
25 import {NoWork} from './ReactFiberExpirationTime';
26 -import {ContextProvider} from 'shared/ReactTypeOfWork';
26 +import {ContextProvider, ClassComponent} from 'shared/ReactTypeOfWork';
27
28 import invariant from 'shared/invariant';
29 import warning from 'shared/warning';
30 +import {
31 + createUpdate,
32 + enqueueUpdate,
33 + ForceUpdate,
34 +} from 'react-reconciler/src/ReactUpdateQueue';
35
36 import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
37 const valueCursor: StackCursor<mixed> = createCursor(null);
33 -const changedBitsCursor: StackCursor<number> = createCursor(0);
38
39 let rendererSigil;
40 if (__DEV__) {
@@ -50,15 +54,13 @@ export function resetContextDependences(): void {
54 lastContextWithAllBitsObserved = null;
55 }
56
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;
59
60 if (isPrimaryRenderer) {
57 - push(changedBitsCursor, context._changedBits, providerFiber);
61 push(valueCursor, context._currentValue, providerFiber);
62
60 - context._currentValue = providerFiber.pendingProps.value;
61 - context._changedBits = changedBits;
63 + context._currentValue = nextValue;
64 if (__DEV__) {
65 warningWithoutStack(
66 context._currentRenderer === undefined ||
@@ -70,11 +72,9 @@ export function pushProvider(providerFiber: Fiber, changedBits: number): void {
72 context._currentRenderer = rendererSigil;
73 }
74 } else {
73 - push(changedBitsCursor, context._changedBits2, providerFiber);
75 push(valueCursor, context._currentValue2, providerFiber);
76
76 - context._currentValue2 = providerFiber.pendingProps.value;
77 - context._changedBits2 = changedBits;
77 + context._currentValue2 = nextValue;
78 if (__DEV__) {
79 warningWithoutStack(
80 context._currentRenderer2 === undefined ||
@@ -89,19 +89,15 @@ export function pushProvider(providerFiber: Fiber, changedBits: number): void {
89 }
90
91 export function popProvider(providerFiber: Fiber): void {
92 - const changedBits = changedBitsCursor.current;
92 const currentValue = valueCursor.current;
93
94 pop(valueCursor, providerFiber);
96 - pop(changedBitsCursor, providerFiber);
95
96 const context: ReactContext<any> = providerFiber.type._context;
97 if (isPrimaryRenderer) {
98 context._currentValue = currentValue;
101 - context._changedBits = changedBits;
99 } else {
100 context._currentValue2 = currentValue;
104 - context._changedBits2 = changedBits;
101 }
102 }
103
@@ -162,6 +158,18 @@ export function propagateContextChange(
158 (dependency.observedBits & changedBits) !== 0
159 ) {
160 // 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 +
173 if (
174 fiber.expirationTime === NoWork ||
175 fiber.expirationTime > renderExpirationTime
@@ -206,13 +214,8 @@ export function propagateContextChange(
214 }
215 node = node.return;
216 }
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;
217 }
218 + nextFiber = fiber.child;
219 dependency = dependency.next;
220 } while (dependency !== null);
221 } else if (fiber.tag === ContextProvider) {
@@ -253,46 +256,13 @@ export function propagateContextChange(
256 export function prepareToReadContext(
257 workInProgress: Fiber,
258 renderExpirationTime: ExpirationTime,
256 -): boolean {
259 +): void {
260 currentlyRenderingFiber = workInProgress;
261 lastContextDependency = null;
262 lastContextWithAllBitsObserved = null;
263
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;
266 }
267
268 export function readContext<T>(
packages/react/src/ReactContext.js
-2
@@ -57,8 +57,6 @@ export function createContext<T>(
57 // Secondary renderers store their context values on separate fields.
58 _currentValue: defaultValue,
59 _currentValue2: defaultValue,
60 - _changedBits: 0,
61 - _changedBits2: 0,
60 // These are circular
61 Provider: (null: any),
62 Consumer: (null: any),
packages/shared/ReactTypes.js
-2
@@ -85,8 +85,6 @@ export type ReactContext<T> = {
85
86 _currentValue: T,
87 _currentValue2: T,
88 - _changedBits: number,
89 - _changedBits2: number,
88
89 // DEV only
90 _currentRenderer?: Object | null,