Skip to content

Commit 1350a85

Browse files
authored
Add unstable context bailout for profiling (#30407)
**This API is not intended to ship. This is a temporary unstable hook for internal performance profiling.** This PR exposes `unstable_useContextWithBailout`, which takes a compare function in addition to Context. The comparison function is run to determine if Context propagation and render should bail out earlier. `unstable_useContextWithBailout` returns the full Context value, same as `useContext`. We can profile this API against `useContext` to better measure the cost of Context value updates and gather more data around propagation and render performance. The bailout logic and test cases are based on #20646 Additionally, this implementation allows multiple values to be compared in one hook by returning a tuple to avoid requiring additional Context consumer hooks.
1 parent f7ee804 commit 1350a85

16 files changed

+524
-17
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
REACT_CONTEXT_TYPE,
3838
} from 'shared/ReactSymbols';
3939
import hasOwnProperty from 'shared/hasOwnProperty';
40+
import type {ContextDependencyWithSelect} from '../../react-reconciler/src/ReactInternalTypes';
4041

4142
type CurrentDispatcherRef = typeof ReactSharedInternals;
4243

@@ -155,7 +156,10 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
155156

156157
let currentFiber: null | Fiber = null;
157158
let currentHook: null | Hook = null;
158-
let currentContextDependency: null | ContextDependency<mixed> = null;
159+
let currentContextDependency:
160+
| null
161+
| ContextDependency<mixed>
162+
| ContextDependencyWithSelect<mixed> = null;
159163

160164
function nextHook(): null | Hook {
161165
const hook = currentHook;

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
enableUseDeferredValueInitialArg,
4848
disableLegacyMode,
4949
enableNoCloningMemoCache,
50+
enableContextProfiling,
5051
} from 'shared/ReactFeatureFlags';
5152
import {
5253
REACT_CONTEXT_TYPE,
@@ -81,7 +82,11 @@ import {
8182
ContinuousEventPriority,
8283
higherEventPriority,
8384
} from './ReactEventPriorities';
84-
import {readContext, checkIfContextChanged} from './ReactFiberNewContext';
85+
import {
86+
readContext,
87+
readContextAndCompare,
88+
checkIfContextChanged,
89+
} from './ReactFiberNewContext';
8590
import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';
8691
import {
8792
LayoutStatic as LayoutStaticEffect,
@@ -1053,6 +1058,16 @@ function updateWorkInProgressHook(): Hook {
10531058
return workInProgressHook;
10541059
}
10551060

1061+
function unstable_useContextWithBailout<T>(
1062+
context: ReactContext<T>,
1063+
select: (T => Array<mixed>) | null,
1064+
): T {
1065+
if (select === null) {
1066+
return readContext(context);
1067+
}
1068+
return readContextAndCompare(context, select);
1069+
}
1070+
10561071
// NOTE: defining two versions of this function to avoid size impact when this feature is disabled.
10571072
// Previously this function was inlined, the additional `memoCache` property makes it not inlined.
10581073
let createFunctionComponentUpdateQueue: () => FunctionComponentUpdateQueue;
@@ -3689,6 +3704,10 @@ if (enableAsyncActions) {
36893704
if (enableAsyncActions) {
36903705
(ContextOnlyDispatcher: Dispatcher).useOptimistic = throwInvalidHookError;
36913706
}
3707+
if (enableContextProfiling) {
3708+
(ContextOnlyDispatcher: Dispatcher).unstable_useContextWithBailout =
3709+
throwInvalidHookError;
3710+
}
36923711

36933712
const HooksDispatcherOnMount: Dispatcher = {
36943713
readContext,
@@ -3728,6 +3747,10 @@ if (enableAsyncActions) {
37283747
if (enableAsyncActions) {
37293748
(HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic;
37303749
}
3750+
if (enableContextProfiling) {
3751+
(HooksDispatcherOnMount: Dispatcher).unstable_useContextWithBailout =
3752+
unstable_useContextWithBailout;
3753+
}
37313754

37323755
const HooksDispatcherOnUpdate: Dispatcher = {
37333756
readContext,
@@ -3767,6 +3790,10 @@ if (enableAsyncActions) {
37673790
if (enableAsyncActions) {
37683791
(HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic;
37693792
}
3793+
if (enableContextProfiling) {
3794+
(HooksDispatcherOnUpdate: Dispatcher).unstable_useContextWithBailout =
3795+
unstable_useContextWithBailout;
3796+
}
37703797

37713798
const HooksDispatcherOnRerender: Dispatcher = {
37723799
readContext,
@@ -3806,6 +3833,10 @@ if (enableAsyncActions) {
38063833
if (enableAsyncActions) {
38073834
(HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic;
38083835
}
3836+
if (enableContextProfiling) {
3837+
(HooksDispatcherOnRerender: Dispatcher).unstable_useContextWithBailout =
3838+
unstable_useContextWithBailout;
3839+
}
38093840

38103841
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
38113842
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
@@ -4019,6 +4050,17 @@ if (__DEV__) {
40194050
return mountOptimistic(passthrough, reducer);
40204051
};
40214052
}
4053+
if (enableContextProfiling) {
4054+
(HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
4055+
function <T>(
4056+
context: ReactContext<T>,
4057+
select: (T => Array<mixed>) | null,
4058+
): T {
4059+
currentHookNameInDev = 'useContext';
4060+
mountHookTypesDev();
4061+
return unstable_useContextWithBailout(context, select);
4062+
};
4063+
}
40224064

40234065
HooksDispatcherOnMountWithHookTypesInDEV = {
40244066
readContext<T>(context: ReactContext<T>): T {
@@ -4200,6 +4242,17 @@ if (__DEV__) {
42004242
return mountOptimistic(passthrough, reducer);
42014243
};
42024244
}
4245+
if (enableContextProfiling) {
4246+
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout =
4247+
function <T>(
4248+
context: ReactContext<T>,
4249+
select: (T => Array<mixed>) | null,
4250+
): T {
4251+
currentHookNameInDev = 'useContext';
4252+
updateHookTypesDev();
4253+
return unstable_useContextWithBailout(context, select);
4254+
};
4255+
}
42034256

42044257
HooksDispatcherOnUpdateInDEV = {
42054258
readContext<T>(context: ReactContext<T>): T {
@@ -4380,6 +4433,17 @@ if (__DEV__) {
43804433
return updateOptimistic(passthrough, reducer);
43814434
};
43824435
}
4436+
if (enableContextProfiling) {
4437+
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4438+
function <T>(
4439+
context: ReactContext<T>,
4440+
select: (T => Array<mixed>) | null,
4441+
): T {
4442+
currentHookNameInDev = 'useContext';
4443+
updateHookTypesDev();
4444+
return unstable_useContextWithBailout(context, select);
4445+
};
4446+
}
43834447

43844448
HooksDispatcherOnRerenderInDEV = {
43854449
readContext<T>(context: ReactContext<T>): T {
@@ -4560,6 +4624,17 @@ if (__DEV__) {
45604624
return rerenderOptimistic(passthrough, reducer);
45614625
};
45624626
}
4627+
if (enableContextProfiling) {
4628+
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4629+
function <T>(
4630+
context: ReactContext<T>,
4631+
select: (T => Array<mixed>) | null,
4632+
): T {
4633+
currentHookNameInDev = 'useContext';
4634+
updateHookTypesDev();
4635+
return unstable_useContextWithBailout(context, select);
4636+
};
4637+
}
45634638

45644639
InvalidNestedHooksDispatcherOnMountInDEV = {
45654640
readContext<T>(context: ReactContext<T>): T {
@@ -4766,6 +4841,18 @@ if (__DEV__) {
47664841
return mountOptimistic(passthrough, reducer);
47674842
};
47684843
}
4844+
if (enableContextProfiling) {
4845+
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4846+
function <T>(
4847+
context: ReactContext<T>,
4848+
select: (T => Array<mixed>) | null,
4849+
): T {
4850+
currentHookNameInDev = 'useContext';
4851+
warnInvalidHookAccess();
4852+
mountHookTypesDev();
4853+
return unstable_useContextWithBailout(context, select);
4854+
};
4855+
}
47694856

47704857
InvalidNestedHooksDispatcherOnUpdateInDEV = {
47714858
readContext<T>(context: ReactContext<T>): T {
@@ -4972,6 +5059,18 @@ if (__DEV__) {
49725059
return updateOptimistic(passthrough, reducer);
49735060
};
49745061
}
5062+
if (enableContextProfiling) {
5063+
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
5064+
function <T>(
5065+
context: ReactContext<T>,
5066+
select: (T => Array<mixed>) | null,
5067+
): T {
5068+
currentHookNameInDev = 'useContext';
5069+
warnInvalidHookAccess();
5070+
updateHookTypesDev();
5071+
return unstable_useContextWithBailout(context, select);
5072+
};
5073+
}
49755074

49765075
InvalidNestedHooksDispatcherOnRerenderInDEV = {
49775076
readContext<T>(context: ReactContext<T>): T {
@@ -5178,4 +5277,16 @@ if (__DEV__) {
51785277
return rerenderOptimistic(passthrough, reducer);
51795278
};
51805279
}
5280+
if (enableContextProfiling) {
5281+
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
5282+
function <T>(
5283+
context: ReactContext<T>,
5284+
select: (T => Array<mixed>) | null,
5285+
): T {
5286+
currentHookNameInDev = 'useContext';
5287+
warnInvalidHookAccess();
5288+
updateHookTypesDev();
5289+
return unstable_useContextWithBailout(context, select);
5290+
};
5291+
}
51815292
}

0 commit comments

Comments
 (0)