Optimize readContext for Subsequent Reads of All Bits (#13248)
This is likely the common case because individual component authors will casually call read on common contexts like the cache, or cache provider. Where as libraries like Relay only call read once per fragment and pass all observed bits at once.
Sebastian Markbåge committed
Jul 21, 2018 at 20:13 UTC
a32c727f2e24bc2e8b5f532420a52b1bae2f98af
1 file changed
+29
-34
packages/react-reconciler/src/ReactFiberNewContext.js
+29
-34
@@ -38,14 +38,14 @@ if (__DEV__) {
38
39
let currentlyRenderingFiber: Fiber | null = null;
40
let lastContextDependency: ContextDependency<mixed> | null = null;
41
-let lastContext: ReactContext<any> | null = null;
41
+let lastContextWithAllBitsObserved: ReactContext<any> | null = null;
42
43
export function resetContextDependences(): void {
44
// This is called right before React yields execution, to ensure `readContext`
45
// cannot be called outside the render phase.
46
currentlyRenderingFiber = null;
47
lastContextDependency = null;
48
- lastContext = null;
48
+ lastContextWithAllBitsObserved = null;
49
}
50
51
export function pushProvider(providerFiber: Fiber): void {
@@ -221,7 +221,7 @@ export function prepareToReadContext(
221
): boolean {
222
currentlyRenderingFiber = workInProgress;
223
lastContextDependency = null;
224
- lastContext = null;
224
+ lastContextWithAllBitsObserved = null;
225
226
const firstContextDependency = workInProgress.firstContextDependency;
227
if (firstContextDependency !== null) {
@@ -264,46 +264,41 @@ export function readContext<T>(
264
context: ReactContext<T>,
265
observedBits: void | number | boolean,
266
): T {
267
- if (typeof observedBits !== 'number') {
268
- if (observedBits === false) {
269
- // Do not observe updates
270
- observedBits = 0;
267
+ if (lastContextWithAllBitsObserved === context) {
268
+ // Nothing to do. We already observe everything in this context.
269
+ } else if (observedBits === false || observedBits === 0) {
270
+ // Do not observe any updates.
271
+ } else {
272
+ let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
273
+ if (
274
+ typeof observedBits !== 'number' ||
275
+ observedBits === maxSigned31BitInt
276
+ ) {
277
+ // Observe all updates.
278
+ lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
279
+ resolvedObservedBits = maxSigned31BitInt;
280
} else {
272
- // Observe all updates
273
- observedBits = maxSigned31BitInt;
281
+ resolvedObservedBits = observedBits;
282
}
275
- }
283
277
- if (lastContext === null) {
278
- invariant(
279
- currentlyRenderingFiber !== null,
280
- 'Context.unstable_read(): Context can only be read while React is ' +
281
- 'rendering, e.g. inside the render method or getDerivedStateFromProps.',
282
- );
283
- // This is the first dependency in the list
284
- currentlyRenderingFiber.firstContextDependency = lastContextDependency = {
284
+ let contextItem = {
285
context: ((context: any): ReactContext<mixed>),
286
- observedBits,
286
+ observedBits: resolvedObservedBits,
287
next: null,
288
};
289
- lastContext = context;
290
- } else {
291
- // `lastContextDependency` is always non-null if `lastContext is.
292
- const lastDependency: ContextDependency<T> = (lastContextDependency: any);
293
- if (lastContext === context) {
294
- // Fast path. The previous context has the same type. We can reuse
295
- // the same node.
296
- lastDependency.observedBits |= observedBits;
289
+
290
+ if (lastContextDependency === null) {
291
+ invariant(
292
+ currentlyRenderingFiber !== null,
293
+ 'Context.unstable_read(): Context can only be read while React is ' +
294
+ 'rendering, e.g. inside the render method or getDerivedStateFromProps.',
295
+ );
296
+ // This is the first dependency in the list
297
+ currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem;
298
} else {
299
// Append a new context item.
299
- lastContextDependency = lastDependency.next = {
300
- context: ((context: any): ReactContext<mixed>),
301
- observedBits,
302
- next: null,
303
- };
304
- lastContext = context;
300
+ lastContextDependency = lastContextDependency.next = contextItem;
301
}
302
}
307
-
303
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
304
}