Skip to content

Commit

Permalink
Add unstable context access API for internal profiling
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack Pope committed Jul 19, 2024
1 parent 66df944 commit 262e651
Show file tree
Hide file tree
Showing 15 changed files with 421 additions and 14 deletions.
2 changes: 1 addition & 1 deletion packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {

let currentFiber: null | Fiber = null;
let currentHook: null | Hook = null;
let currentContextDependency: null | ContextDependency<mixed> = null;
let currentContextDependency: null | ContextDependency<mixed, mixed> = null;

function nextHook(): null | Hook {
const hook = currentHook;
Expand Down
89 changes: 88 additions & 1 deletion packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
enableUseDeferredValueInitialArg,
disableLegacyMode,
enableNoCloningMemoCache,
enableContextProfiling,
} from 'shared/ReactFeatureFlags';
import {
REACT_CONTEXT_TYPE,
Expand Down Expand Up @@ -81,7 +82,11 @@ import {
ContinuousEventPriority,
higherEventPriority,
} from './ReactEventPriorities';
import {readContext, checkIfContextChanged} from './ReactFiberNewContext';
import {
readContext,
readContextAndCompare,
checkIfContextChanged,
} from './ReactFiberNewContext';
import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';
import {
LayoutStatic as LayoutStaticEffect,
Expand Down Expand Up @@ -1053,6 +1058,13 @@ function updateWorkInProgressHook(): Hook {
return workInProgressHook;
}

function unstable_useContextWithBailout<T>(
context: ReactContext<T>,
compare: void | (T => mixed),
): T {
return readContextAndCompare(context, compare);
}

// NOTE: defining two versions of this function to avoid size impact when this feature is disabled.
// Previously this function was inlined, the additional `memoCache` property makes it not inlined.
let createFunctionComponentUpdateQueue: () => FunctionComponentUpdateQueue;
Expand Down Expand Up @@ -3689,6 +3701,10 @@ if (enableAsyncActions) {
if (enableAsyncActions) {
(ContextOnlyDispatcher: Dispatcher).useOptimistic = throwInvalidHookError;
}
if (enableContextProfiling) {
(ContextOnlyDispatcher: Dispatcher).unstable_useContextWithBailout =
throwInvalidHookError;
}

const HooksDispatcherOnMount: Dispatcher = {
readContext,
Expand Down Expand Up @@ -3728,6 +3744,10 @@ if (enableAsyncActions) {
if (enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic;
}
if (enableContextProfiling) {
(HooksDispatcherOnMount: Dispatcher).unstable_useContextWithBailout =
unstable_useContextWithBailout;
}

const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
Expand Down Expand Up @@ -3767,6 +3787,10 @@ if (enableAsyncActions) {
if (enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic;
}
if (enableContextProfiling) {
(HooksDispatcherOnUpdate: Dispatcher).unstable_useContextWithBailout =
unstable_useContextWithBailout;
}

const HooksDispatcherOnRerender: Dispatcher = {
readContext,
Expand Down Expand Up @@ -3806,6 +3830,10 @@ if (enableAsyncActions) {
if (enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic;
}
if (enableContextProfiling) {
(HooksDispatcherOnRerender: Dispatcher).unstable_useContextWithBailout =
unstable_useContextWithBailout;
}

let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
Expand Down Expand Up @@ -4019,6 +4047,14 @@ if (__DEV__) {
return mountOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}

HooksDispatcherOnMountWithHookTypesInDEV = {
readContext<T>(context: ReactContext<T>): T {
Expand Down Expand Up @@ -4200,6 +4236,14 @@ if (__DEV__) {
return mountOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}

HooksDispatcherOnUpdateInDEV = {
readContext<T>(context: ReactContext<T>): T {
Expand Down Expand Up @@ -4380,6 +4424,14 @@ if (__DEV__) {
return updateOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}

HooksDispatcherOnRerenderInDEV = {
readContext<T>(context: ReactContext<T>): T {
Expand Down Expand Up @@ -4560,6 +4612,14 @@ if (__DEV__) {
return rerenderOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}

InvalidNestedHooksDispatcherOnMountInDEV = {
readContext<T>(context: ReactContext<T>): T {
Expand Down Expand Up @@ -4766,6 +4826,15 @@ if (__DEV__) {
return mountOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
mountHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}

InvalidNestedHooksDispatcherOnUpdateInDEV = {
readContext<T>(context: ReactContext<T>): T {
Expand Down Expand Up @@ -4972,6 +5041,15 @@ if (__DEV__) {
return updateOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}

InvalidNestedHooksDispatcherOnRerenderInDEV = {
readContext<T>(context: ReactContext<T>): T {
Expand Down Expand Up @@ -5178,4 +5256,13 @@ if (__DEV__) {
return rerenderOptimistic(passthrough, reducer);
};
}
if (enableContextProfiling) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return unstable_useContextWithBailout(context, compare);
};
}
}
80 changes: 73 additions & 7 deletions packages/react-reconciler/src/ReactFiberNewContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import {
getHostTransitionProvider,
HostTransitionContext,
} from './ReactFiberHostContext';
import isArray from '../../shared/isArray';
import {enableContextProfiling} from '../../shared/ReactFeatureFlags';

const valueCursor: StackCursor<mixed> = createCursor(null);

Expand All @@ -70,7 +72,7 @@ if (__DEV__) {
}

let currentlyRenderingFiber: Fiber | null = null;
let lastContextDependency: ContextDependency<mixed> | null = null;
let lastContextDependency: ContextDependency<mixed, mixed> | null = null;
let lastFullyObservedContext: ReactContext<any> | null = null;

let isDisallowedContextReadInDEV: boolean = false;
Expand Down Expand Up @@ -400,8 +402,22 @@ function propagateContextChanges<T>(
findContext: for (let i = 0; i < contexts.length; i++) {
const context: ReactContext<T> = contexts[i];
// Check if the context matches.
// TODO: Compare selected values to bail out early.
if (dependency.context === context) {
const compare = dependency.compare;
if (enableContextProfiling && compare != null) {
const newValue = isPrimaryRenderer
? dependency.context._currentValue
: dependency.context._currentValue2;
if (
!checkIfComparedContextValuesChanged(
dependency.lastComparedValue,
compare(newValue),
)
) {
// Compared value hasn't changed. Bail out early.
continue findContext;
}
}
// Match! Schedule an update on this fiber.

// In the lazy implementation, don't mark a dirty flag on the
Expand Down Expand Up @@ -641,6 +657,28 @@ function propagateParentContextChanges(
workInProgress.flags |= DidPropagateContext;
}

function checkIfComparedContextValuesChanged(
oldComparedValue: mixed,
newComparedValue: mixed,
): boolean {
if (isArray(oldComparedValue) && isArray(newComparedValue)) {
for (
let i = 0;
i < oldComparedValue.length && i < newComparedValue.length;
i++
) {
if (!is(newComparedValue[i], oldComparedValue[i])) {
return true;
}
}
} else {
if (!is(newComparedValue, oldComparedValue)) {
return true;
}
}
return false;
}

export function checkIfContextChanged(
currentDependencies: Dependencies,
): boolean {
Expand All @@ -659,8 +697,20 @@ export function checkIfContextChanged(
? context._currentValue
: context._currentValue2;
const oldValue = dependency.memoizedValue;
if (!is(newValue, oldValue)) {
return true;
const compare = dependency.compare;
if (enableContextProfiling && compare != null) {
if (
checkIfComparedContextValuesChanged(
dependency.lastComparedValue,
compare(newValue),
)
) {
return true;
}
} else {
if (!is(newValue, oldValue)) {
return true;
}
}
dependency = dependency.next;
}
Expand Down Expand Up @@ -694,6 +744,17 @@ export function prepareToReadContext(
}
}

export function readContextAndCompare<C>(
context: ReactContext<C>,
compare: void | (C => mixed),
): C {
if (!enableLazyContextPropagation) {
return readContext(context);
}

return readContextForConsumer(currentlyRenderingFiber, context, compare);
}

export function readContext<T>(context: ReactContext<T>): T {
if (__DEV__) {
// This warning would fire if you read context inside a Hook like useMemo.
Expand Down Expand Up @@ -721,10 +782,13 @@ export function readContextDuringReconciliation<T>(
return readContextForConsumer(consumer, context);
}

function readContextForConsumer<T>(
type ContextCompare<C, S> = C => S;

function readContextForConsumer<C, S>(
consumer: Fiber | null,
context: ReactContext<T>,
): T {
context: ReactContext<C>,
compare?: void | (C => S),
): C {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
Expand All @@ -736,6 +800,8 @@ function readContextForConsumer<T>(
context: ((context: any): ReactContext<mixed>),
memoizedValue: value,
next: null,
compare: ((compare: any): ContextCompare<mixed, mixed> | null),
lastComparedValue: compare != null ? compare(value) : null,
};

if (lastContextDependency === null) {
Expand Down
16 changes: 11 additions & 5 deletions packages/react-reconciler/src/ReactInternalTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,18 @@ export type HookType =
| 'useFormState'
| 'useActionState';

export type ContextDependency<T> = {
context: ReactContext<T>,
next: ContextDependency<mixed> | null,
memoizedValue: T,
export type ContextDependency<C, S> = {
context: ReactContext<C>,
next: ContextDependency<mixed, mixed> | null,
memoizedValue: C,
compare: (C => S) | null,
lastComparedValue: S | null,
...
};

export type Dependencies = {
lanes: Lanes,
firstContext: ContextDependency<mixed> | null,
firstContext: ContextDependency<mixed, mixed> | null,
...
};

Expand Down Expand Up @@ -384,6 +386,10 @@ export type Dispatcher = {
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>],
unstable_useContextWithBailout?: <T>(
context: ReactContext<T>,
compare: void | (T => mixed),
) => T,
useContext<T>(context: ReactContext<T>): T,
useRef<T>(initialValue: T): {current: T},
useEffect(
Expand Down
Loading

0 comments on commit 262e651

Please sign in to comment.