Skip to content

Commit

Permalink
Run selectors during propagation to bail out early
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Jul 8, 2021
1 parent b0b7cd6 commit fffc35b
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 54 deletions.
4 changes: 2 additions & 2 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Expand Up @@ -114,9 +114,9 @@ function readContext<T>(context: ReactContext<T>): T {
return context._currentValue;
}

function useContext<T, S>(
function useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
hookLog.push({
primitive: 'Context',
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dom/src/server/ReactPartialRendererHooks.js
Expand Up @@ -235,9 +235,9 @@ function readContext<T>(context: ReactContext<T>): T {
return context[threadID];
}

function useContext<T, S>(
function useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
if (__DEV__) {
currentHookNameInDev = 'useContext';
Expand Down
32 changes: 16 additions & 16 deletions packages/react-reconciler/src/ReactFiberHooks.new.js
Expand Up @@ -680,9 +680,9 @@ function updateWorkInProgressHook(): Hook {
return workInProgressHook;
}

function useContext<T, S>(
function useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
if (options !== undefined) {
const selector = options.unstable_selector;
Expand Down Expand Up @@ -2216,9 +2216,9 @@ if (__DEV__) {
checkDepsAreArrayDev(deps);
return mountCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
Expand Down Expand Up @@ -2347,9 +2347,9 @@ if (__DEV__) {
updateHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
Expand Down Expand Up @@ -2474,9 +2474,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
Expand Down Expand Up @@ -2602,9 +2602,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
Expand Down Expand Up @@ -2731,9 +2731,9 @@ if (__DEV__) {
mountHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
Expand Down Expand Up @@ -2873,9 +2873,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
Expand Down Expand Up @@ -3016,9 +3016,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
Expand Down
32 changes: 16 additions & 16 deletions packages/react-reconciler/src/ReactFiberHooks.old.js
Expand Up @@ -680,9 +680,9 @@ function updateWorkInProgressHook(): Hook {
return workInProgressHook;
}

function useContext<T, S>(
function useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
if (options !== undefined) {
const selector = options.unstable_selector;
Expand Down Expand Up @@ -2216,9 +2216,9 @@ if (__DEV__) {
checkDepsAreArrayDev(deps);
return mountCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
Expand Down Expand Up @@ -2347,9 +2347,9 @@ if (__DEV__) {
updateHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
Expand Down Expand Up @@ -2474,9 +2474,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
Expand Down Expand Up @@ -2602,9 +2602,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
Expand Down Expand Up @@ -2731,9 +2731,9 @@ if (__DEV__) {
mountHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
Expand Down Expand Up @@ -2873,9 +2873,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
Expand Down Expand Up @@ -3016,9 +3016,9 @@ if (__DEV__) {
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
Expand Down
25 changes: 18 additions & 7 deletions packages/react-reconciler/src/ReactFiberNewContext.new.js
Expand Up @@ -212,7 +212,6 @@ function propagateContextChange_eager<T>(
let dependency = list.firstContext;
while (dependency !== null) {
// Check if the context matches.
// TODO: Compare selected values to bail out early.
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
if (fiber.tag === ClassComponent) {
Expand Down Expand Up @@ -348,8 +347,19 @@ 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 selector = dependency.selector;
if (selector !== null) {
const newValue = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
const newSelectedValue = selector(newValue);
const oldSelectedValue = dependency.selectedValue;
if (is(oldSelectedValue, selector(newSelectedValue))) {
// Selected value hasn't changed. Bail out early.
continue findContext;
}
}
// Match! Schedule an update on this fiber.

// In the lazy implemenation, don't mark a dirty flag on the
Expand Down Expand Up @@ -571,10 +581,8 @@ export function checkIfContextChanged(currentDependencies: Dependencies) {
const oldValue = dependency.memoizedValue;
const selector = dependency.selector;
if (selector !== null) {
// TODO: Alternatively, we could store the selected value on the context.
// However, we expect selectors to do nothing except access a subfield,
// so this is probably fine, too.
if (!is(selector(newValue), selector(oldValue))) {
const oldSelectedValue = dependency.selectedValue;
if (!is(selector(newValue), oldSelectedValue)) {
return true;
}
} else {
Expand Down Expand Up @@ -657,8 +665,11 @@ function readContextImpl<C, S>(
const contextItem = {
context: ((context: any): ReactContext<mixed>),
selector: ((selector: any): ContextSelector<mixed, mixed> | null),
// TODO: Store selected value so we can compare to that during propagation
memoizedValue: value,
// TODO: If useContextSelector becomes a built-in API, then
// readContextWithSelector should return the selected value so that we
// don't call the selector twice. Will need to inline readContextImpl.
selectedValue: selector !== null ? selector(value) : null,
next: null,
};

Expand Down
25 changes: 18 additions & 7 deletions packages/react-reconciler/src/ReactFiberNewContext.old.js
Expand Up @@ -212,7 +212,6 @@ function propagateContextChange_eager<T>(
let dependency = list.firstContext;
while (dependency !== null) {
// Check if the context matches.
// TODO: Compare selected values to bail out early.
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
if (fiber.tag === ClassComponent) {
Expand Down Expand Up @@ -348,8 +347,19 @@ 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 selector = dependency.selector;
if (selector !== null) {
const newValue = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
const newSelectedValue = selector(newValue);
const oldSelectedValue = dependency.selectedValue;
if (is(oldSelectedValue, selector(newSelectedValue))) {
// Selected value hasn't changed. Bail out early.
continue findContext;
}
}
// Match! Schedule an update on this fiber.

// In the lazy implemenation, don't mark a dirty flag on the
Expand Down Expand Up @@ -571,10 +581,8 @@ export function checkIfContextChanged(currentDependencies: Dependencies) {
const oldValue = dependency.memoizedValue;
const selector = dependency.selector;
if (selector !== null) {
// TODO: Alternatively, we could store the selected value on the context.
// However, we expect selectors to do nothing except access a subfield,
// so this is probably fine, too.
if (!is(selector(newValue), selector(oldValue))) {
const oldSelectedValue = dependency.selectedValue;
if (!is(selector(newValue), oldSelectedValue)) {
return true;
}
} else {
Expand Down Expand Up @@ -657,8 +665,11 @@ function readContextImpl<C, S>(
const contextItem = {
context: ((context: any): ReactContext<mixed>),
selector: ((selector: any): ContextSelector<mixed, mixed> | null),
// TODO: Store selected value so we can compare to that during propagation
memoizedValue: value,
// TODO: If useContextSelector becomes a built-in API, then
// readContextWithSelector should return the selected value so that we
// don't call the selector twice. Will need to inline readContextImpl.
selectedValue: selector !== null ? selector(value) : null,
next: null,
};

Expand Down
5 changes: 3 additions & 2 deletions packages/react-reconciler/src/ReactInternalTypes.js
Expand Up @@ -51,6 +51,7 @@ export type ContextDependency<C, S> = {
selector: (C => S) | null,
next: ContextDependency<mixed, mixed> | null,
memoizedValue: C,
selectedValue: S | null,
...
};

Expand Down Expand Up @@ -282,9 +283,9 @@ export type Dispatcher = {|
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>],
useContext<T, S>(
useContext<T>(
context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T,
useRef<T>(initialValue: T): {|current: T|},
useEffect(
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/ReactHooks.js
Expand Up @@ -47,9 +47,9 @@ export function getCacheForType<T>(resourceType: () => T): T {
return dispatcher.getCacheForType(resourceType);
}

export function useContext<T, S>(
export function useContext<T>(
Context: ReactContext<T>,
options?: {unstable_selector?: T => S},
options?: {unstable_selector?: T => mixed},
): T {
const dispatcher = resolveDispatcher();
if (__DEV__) {
Expand Down

0 comments on commit fffc35b

Please sign in to comment.