mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Clean up context access profiling experiment (#31806)
We introduced the `unstable_useContextWithBailout` API to run compiler based experiments. This API was designed to be an experiment proxy for alternative approaches which would be heavier to implement. The experiment turned out to be inconclusive. Since most of our performance critical usage is already optimized, we weren't able to find a clear win with this approach. Since we don't have further plans for this API, let's clean it up.
This commit is contained in:
+1
-29
@@ -20,7 +20,6 @@ import type {
|
||||
Dependencies,
|
||||
Fiber,
|
||||
Dispatcher as DispatcherType,
|
||||
ContextDependencyWithSelect,
|
||||
} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {TransitionStatus} from 'react-reconciler/src/ReactFiberConfig';
|
||||
|
||||
@@ -76,13 +75,6 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
try {
|
||||
// Use all hooks here to add them to the hook log.
|
||||
Dispatcher.useContext(({_currentValue: null}: any));
|
||||
if (typeof Dispatcher.unstable_useContextWithBailout === 'function') {
|
||||
// This type check is for Flow only.
|
||||
Dispatcher.unstable_useContextWithBailout(
|
||||
({_currentValue: null}: any),
|
||||
null,
|
||||
);
|
||||
}
|
||||
Dispatcher.useState(null);
|
||||
Dispatcher.useReducer((s: mixed, a: mixed) => s, null);
|
||||
Dispatcher.useRef(null);
|
||||
@@ -150,10 +142,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
|
||||
let currentFiber: null | Fiber = null;
|
||||
let currentHook: null | Hook = null;
|
||||
let currentContextDependency:
|
||||
| null
|
||||
| ContextDependency<mixed>
|
||||
| ContextDependencyWithSelect<mixed> = null;
|
||||
let currentContextDependency: null | ContextDependency<mixed> = null;
|
||||
|
||||
function nextHook(): null | Hook {
|
||||
const hook = currentHook;
|
||||
@@ -274,22 +263,6 @@ function useContext<T>(context: ReactContext<T>): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
function unstable_useContextWithBailout<T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
const value = readContext(context);
|
||||
hookLog.push({
|
||||
displayName: context.displayName || null,
|
||||
primitive: 'ContextWithBailout',
|
||||
stackError: new Error(),
|
||||
value: value,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'ContextWithBailout',
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
function useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
@@ -764,7 +737,6 @@ const Dispatcher: DispatcherType = {
|
||||
useCacheRefresh,
|
||||
useCallback,
|
||||
useContext,
|
||||
unstable_useContextWithBailout,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
|
||||
+2
-113
@@ -39,12 +39,11 @@ import {
|
||||
enableLazyContextPropagation,
|
||||
enableTransitionTracing,
|
||||
enableUseEffectEventHook,
|
||||
enableUseResourceEffectHook,
|
||||
enableLegacyCache,
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
disableLegacyMode,
|
||||
enableNoCloningMemoCache,
|
||||
enableContextProfiling,
|
||||
enableUseResourceEffectHook,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
REACT_CONTEXT_TYPE,
|
||||
@@ -78,11 +77,7 @@ import {
|
||||
ContinuousEventPriority,
|
||||
higherEventPriority,
|
||||
} from './ReactEventPriorities';
|
||||
import {
|
||||
readContext,
|
||||
readContextAndCompare,
|
||||
checkIfContextChanged,
|
||||
} from './ReactFiberNewContext';
|
||||
import {readContext, checkIfContextChanged} from './ReactFiberNewContext';
|
||||
import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';
|
||||
import {
|
||||
LayoutStatic as LayoutStaticEffect,
|
||||
@@ -1111,16 +1106,6 @@ function updateWorkInProgressHook(): Hook {
|
||||
return workInProgressHook;
|
||||
}
|
||||
|
||||
function unstable_useContextWithBailout<T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
if (select === null) {
|
||||
return readContext(context);
|
||||
}
|
||||
return readContextAndCompare(context, select);
|
||||
}
|
||||
|
||||
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
|
||||
return {
|
||||
lastEffect: null,
|
||||
@@ -3958,10 +3943,6 @@ if (enableUseEffectEventHook) {
|
||||
if (enableUseResourceEffectHook) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError;
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(ContextOnlyDispatcher: Dispatcher).unstable_useContextWithBailout =
|
||||
throwInvalidHookError;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnMount: Dispatcher = {
|
||||
readContext,
|
||||
@@ -3995,10 +3976,6 @@ if (enableUseEffectEventHook) {
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect;
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnMount: Dispatcher).unstable_useContextWithBailout =
|
||||
unstable_useContextWithBailout;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
readContext,
|
||||
@@ -4033,10 +4010,6 @@ if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useResourceEffect =
|
||||
updateResourceEffect;
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).unstable_useContextWithBailout =
|
||||
unstable_useContextWithBailout;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnRerender: Dispatcher = {
|
||||
readContext,
|
||||
@@ -4071,10 +4044,6 @@ if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useResourceEffect =
|
||||
updateResourceEffect;
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).unstable_useContextWithBailout =
|
||||
unstable_useContextWithBailout;
|
||||
}
|
||||
|
||||
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
|
||||
@@ -4296,17 +4265,6 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
mountHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnMountWithHookTypesInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4494,17 +4452,6 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
updateHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnUpdateInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4692,17 +4639,6 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
updateHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -4890,17 +4826,6 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
updateHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnMountInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -5114,18 +5039,6 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnUpdateInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -5339,18 +5252,6 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
@@ -5564,16 +5465,4 @@ if (__DEV__) {
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableContextProfiling) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
|
||||
function <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return unstable_useContextWithBailout(context, select);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+6
-131
@@ -12,7 +12,6 @@ import type {
|
||||
Fiber,
|
||||
ContextDependency,
|
||||
Dependencies,
|
||||
ContextDependencyWithSelect,
|
||||
} from './ReactInternalTypes';
|
||||
import type {StackCursor} from './ReactFiberStack';
|
||||
import type {Lanes} from './ReactFiberLane';
|
||||
@@ -48,8 +47,6 @@ import {
|
||||
enableRenderableContext,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {getHostTransitionProvider} from './ReactFiberHostContext';
|
||||
import isArray from '../../shared/isArray';
|
||||
import {enableContextProfiling} from '../../shared/ReactFeatureFlags';
|
||||
|
||||
const valueCursor: StackCursor<mixed> = createCursor(null);
|
||||
|
||||
@@ -69,10 +66,7 @@ if (__DEV__) {
|
||||
}
|
||||
|
||||
let currentlyRenderingFiber: Fiber | null = null;
|
||||
let lastContextDependency:
|
||||
| ContextDependency<mixed>
|
||||
| ContextDependencyWithSelect<mixed>
|
||||
| null = null;
|
||||
let lastContextDependency: ContextDependency<mixed> | null = null;
|
||||
|
||||
let isDisallowedContextReadInDEV: boolean = false;
|
||||
|
||||
@@ -401,23 +395,6 @@ function propagateContextChanges<T>(
|
||||
const context: ReactContext<T> = contexts[i];
|
||||
// Check if the context matches.
|
||||
if (dependency.context === context) {
|
||||
if (enableContextProfiling) {
|
||||
const select = dependency.select;
|
||||
if (select != null && dependency.lastSelectedValue != null) {
|
||||
const newValue = isPrimaryRenderer
|
||||
? dependency.context._currentValue
|
||||
: dependency.context._currentValue2;
|
||||
if (
|
||||
!checkIfSelectedContextValuesChanged(
|
||||
dependency.lastSelectedValue,
|
||||
select(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
|
||||
@@ -657,29 +634,6 @@ function propagateParentContextChanges(
|
||||
workInProgress.flags |= DidPropagateContext;
|
||||
}
|
||||
|
||||
function checkIfSelectedContextValuesChanged(
|
||||
oldComparedValue: Array<mixed>,
|
||||
newComparedValue: Array<mixed>,
|
||||
): boolean {
|
||||
// We have an implicit contract that compare functions must return arrays.
|
||||
// This allows us to compare multiple values in the same context access
|
||||
// since compiling to additional hook calls regresses perf.
|
||||
if (isArray(oldComparedValue) && isArray(newComparedValue)) {
|
||||
if (oldComparedValue.length !== newComparedValue.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = 0; i < oldComparedValue.length; i++) {
|
||||
if (!is(newComparedValue[i], oldComparedValue[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Compared context values must be arrays');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function checkIfContextChanged(
|
||||
currentDependencies: Dependencies,
|
||||
): boolean {
|
||||
@@ -698,23 +652,8 @@ export function checkIfContextChanged(
|
||||
? context._currentValue
|
||||
: context._currentValue2;
|
||||
const oldValue = dependency.memoizedValue;
|
||||
if (
|
||||
enableContextProfiling &&
|
||||
dependency.select != null &&
|
||||
dependency.lastSelectedValue != null
|
||||
) {
|
||||
if (
|
||||
checkIfSelectedContextValuesChanged(
|
||||
dependency.lastSelectedValue,
|
||||
dependency.select(newValue),
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!is(newValue, oldValue)) {
|
||||
return true;
|
||||
}
|
||||
if (!is(newValue, oldValue)) {
|
||||
return true;
|
||||
}
|
||||
dependency = dependency.next;
|
||||
}
|
||||
@@ -747,21 +686,6 @@ export function prepareToReadContext(
|
||||
}
|
||||
}
|
||||
|
||||
export function readContextAndCompare<C>(
|
||||
context: ReactContext<C>,
|
||||
select: C => Array<mixed>,
|
||||
): C {
|
||||
if (!(enableLazyContextPropagation && enableContextProfiling)) {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
|
||||
return readContextForConsumer_withSelect(
|
||||
currentlyRenderingFiber,
|
||||
context,
|
||||
select,
|
||||
);
|
||||
}
|
||||
|
||||
export function readContext<T>(context: ReactContext<T>): T {
|
||||
if (__DEV__) {
|
||||
// This warning would fire if you read context inside a Hook like useMemo.
|
||||
@@ -789,59 +713,10 @@ export function readContextDuringReconciliation<T>(
|
||||
return readContextForConsumer(consumer, context);
|
||||
}
|
||||
|
||||
function readContextForConsumer_withSelect<C>(
|
||||
function readContextForConsumer<T>(
|
||||
consumer: Fiber | null,
|
||||
context: ReactContext<C>,
|
||||
select: C => Array<mixed>,
|
||||
): C {
|
||||
const value = isPrimaryRenderer
|
||||
? context._currentValue
|
||||
: context._currentValue2;
|
||||
|
||||
const contextItem = {
|
||||
context: ((context: any): ReactContext<mixed>),
|
||||
memoizedValue: value,
|
||||
next: null,
|
||||
select: ((select: any): (context: mixed) => Array<mixed>),
|
||||
lastSelectedValue: select(value),
|
||||
};
|
||||
|
||||
if (lastContextDependency === null) {
|
||||
if (consumer === null) {
|
||||
throw new Error(
|
||||
'Context can only be read while React is rendering. ' +
|
||||
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
|
||||
'In function components, you can read it directly in the function body, but not ' +
|
||||
'inside Hooks like useReducer() or useMemo().',
|
||||
);
|
||||
}
|
||||
|
||||
// This is the first dependency for this component. Create a new list.
|
||||
lastContextDependency = contextItem;
|
||||
consumer.dependencies = __DEV__
|
||||
? {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
_debugThenableState: null,
|
||||
}
|
||||
: {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
if (enableLazyContextPropagation) {
|
||||
consumer.flags |= NeedsPropagation;
|
||||
}
|
||||
} else {
|
||||
// Append a new context item.
|
||||
lastContextDependency = lastContextDependency.next = contextItem;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function readContextForConsumer<C>(
|
||||
consumer: Fiber | null,
|
||||
context: ReactContext<C>,
|
||||
): C {
|
||||
context: ReactContext<T>,
|
||||
): T {
|
||||
const value = isPrimaryRenderer
|
||||
? context._currentValue
|
||||
: context._currentValue2;
|
||||
|
||||
+7
-20
@@ -63,27 +63,18 @@ export type HookType =
|
||||
| 'useFormState'
|
||||
| 'useActionState';
|
||||
|
||||
export type ContextDependency<C> = {
|
||||
context: ReactContext<C>,
|
||||
next: ContextDependency<mixed> | ContextDependencyWithSelect<mixed> | null,
|
||||
memoizedValue: C,
|
||||
};
|
||||
|
||||
export type ContextDependencyWithSelect<C> = {
|
||||
context: ReactContext<C>,
|
||||
next: ContextDependency<mixed> | ContextDependencyWithSelect<mixed> | null,
|
||||
memoizedValue: C,
|
||||
select: C => Array<mixed>,
|
||||
lastSelectedValue: ?Array<mixed>,
|
||||
export type ContextDependency<T> = {
|
||||
context: ReactContext<T>,
|
||||
next: ContextDependency<mixed> | null,
|
||||
memoizedValue: T,
|
||||
...
|
||||
};
|
||||
|
||||
export type Dependencies = {
|
||||
lanes: Lanes,
|
||||
firstContext:
|
||||
| ContextDependency<mixed>
|
||||
| ContextDependencyWithSelect<mixed>
|
||||
| null,
|
||||
firstContext: ContextDependency<mixed> | null,
|
||||
_debugThenableState?: null | ThenableState, // DEV-only
|
||||
...
|
||||
};
|
||||
|
||||
export type MemoCache = {
|
||||
@@ -401,10 +392,6 @@ export type Dispatcher = {
|
||||
initialArg: I,
|
||||
init?: (I) => S,
|
||||
): [S, Dispatch<A>],
|
||||
unstable_useContextWithBailout?: <T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
) => T,
|
||||
useContext<T>(context: ReactContext<T>): T,
|
||||
useRef<T>(initialValue: T): {current: T},
|
||||
useEffect(
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let assertLog;
|
||||
let useState;
|
||||
let useContext;
|
||||
let unstable_useContextWithBailout;
|
||||
|
||||
describe('ReactContextWithBailout', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
const testUtils = require('internal-test-utils');
|
||||
act = testUtils.act;
|
||||
assertLog = testUtils.assertLog;
|
||||
useState = React.useState;
|
||||
useContext = React.useContext;
|
||||
unstable_useContextWithBailout = React.unstable_useContextWithBailout;
|
||||
});
|
||||
|
||||
function Text({text}) {
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
// @gate enableLazyContextPropagation && enableContextProfiling
|
||||
test('unstable_useContextWithBailout basic usage', async () => {
|
||||
const Context = React.createContext();
|
||||
|
||||
let setContext;
|
||||
function App() {
|
||||
const [context, _setContext] = useState({a: 'A0', b: 'B0', c: 'C0'});
|
||||
setContext = _setContext;
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
<Indirection />
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Intermediate parent that bails out. Children will only re-render when the
|
||||
// context changes.
|
||||
const Indirection = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
A: <A />, B: <B />, C: <C />, AB: <AB />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
function A() {
|
||||
const {a} = unstable_useContextWithBailout(Context, context => [
|
||||
context.a,
|
||||
]);
|
||||
return <Text text={a} />;
|
||||
}
|
||||
|
||||
function B() {
|
||||
const {b} = unstable_useContextWithBailout(Context, context => [
|
||||
context.b,
|
||||
]);
|
||||
return <Text text={b} />;
|
||||
}
|
||||
|
||||
function C() {
|
||||
const {c} = unstable_useContextWithBailout(Context, context => [
|
||||
context.c,
|
||||
]);
|
||||
return <Text text={c} />;
|
||||
}
|
||||
|
||||
function AB() {
|
||||
const {a, b} = unstable_useContextWithBailout(Context, context => [
|
||||
context.a,
|
||||
context.b,
|
||||
]);
|
||||
return <Text text={a + b} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['A0', 'B0', 'C0', 'A0B0']);
|
||||
expect(root).toMatchRenderedOutput('A: A0, B: B0, C: C0, AB: A0B0');
|
||||
|
||||
// Update a. Only the A and AB consumer should re-render.
|
||||
await act(async () => {
|
||||
setContext({a: 'A1', c: 'C0', b: 'B0'});
|
||||
});
|
||||
assertLog(['A1', 'A1B0']);
|
||||
expect(root).toMatchRenderedOutput('A: A1, B: B0, C: C0, AB: A1B0');
|
||||
|
||||
// Update b. Only the B and AB consumer should re-render.
|
||||
await act(async () => {
|
||||
setContext({a: 'A1', b: 'B1', c: 'C0'});
|
||||
});
|
||||
assertLog(['B1', 'A1B1']);
|
||||
expect(root).toMatchRenderedOutput('A: A1, B: B1, C: C0, AB: A1B1');
|
||||
|
||||
// Update c. Only the C consumer should re-render.
|
||||
await act(async () => {
|
||||
setContext({a: 'A1', b: 'B1', c: 'C1'});
|
||||
});
|
||||
assertLog(['C1']);
|
||||
expect(root).toMatchRenderedOutput('A: A1, B: B1, C: C1, AB: A1B1');
|
||||
});
|
||||
|
||||
// @gate enableLazyContextPropagation && enableContextProfiling
|
||||
test('unstable_useContextWithBailout and useContext subscribing to same context in same component', async () => {
|
||||
const Context = React.createContext();
|
||||
|
||||
let setContext;
|
||||
function App() {
|
||||
const [context, _setContext] = useState({a: 0, b: 0, unrelated: 0});
|
||||
setContext = _setContext;
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
<Indirection />
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Intermediate parent that bails out. Children will only re-render when the
|
||||
// context changes.
|
||||
const Indirection = React.memo(() => {
|
||||
return <Child />;
|
||||
});
|
||||
|
||||
function Child() {
|
||||
const {a} = unstable_useContextWithBailout(Context, context => [
|
||||
context.a,
|
||||
]);
|
||||
const context = useContext(Context);
|
||||
return <Text text={`A: ${a}, B: ${context.b}`} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['A: 0, B: 0']);
|
||||
expect(root).toMatchRenderedOutput('A: 0, B: 0');
|
||||
|
||||
// Update an unrelated field that isn't used by the component. The context
|
||||
// attempts to bail out, but the normal context forces an update.
|
||||
await act(async () => {
|
||||
setContext({a: 0, b: 0, unrelated: 1});
|
||||
});
|
||||
assertLog(['A: 0, B: 0']);
|
||||
expect(root).toMatchRenderedOutput('A: 0, B: 0');
|
||||
});
|
||||
|
||||
// @gate enableLazyContextPropagation && enableContextProfiling
|
||||
test('unstable_useContextWithBailout and useContext subscribing to different contexts in same component', async () => {
|
||||
const ContextA = React.createContext();
|
||||
const ContextB = React.createContext();
|
||||
|
||||
let setContextA;
|
||||
let setContextB;
|
||||
function App() {
|
||||
const [a, _setContextA] = useState({a: 0, unrelated: 0});
|
||||
const [b, _setContextB] = useState(0);
|
||||
setContextA = _setContextA;
|
||||
setContextB = _setContextB;
|
||||
return (
|
||||
<ContextA.Provider value={a}>
|
||||
<ContextB.Provider value={b}>
|
||||
<Indirection />
|
||||
</ContextB.Provider>
|
||||
</ContextA.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Intermediate parent that bails out. Children will only re-render when the
|
||||
// context changes.
|
||||
const Indirection = React.memo(() => {
|
||||
return <Child />;
|
||||
});
|
||||
|
||||
function Child() {
|
||||
const {a} = unstable_useContextWithBailout(ContextA, context => [
|
||||
context.a,
|
||||
]);
|
||||
const b = useContext(ContextB);
|
||||
return <Text text={`A: ${a}, B: ${b}`} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['A: 0, B: 0']);
|
||||
expect(root).toMatchRenderedOutput('A: 0, B: 0');
|
||||
|
||||
// Update a field in A that isn't part of the compared context. It should
|
||||
// bail out.
|
||||
await act(async () => {
|
||||
setContextA({a: 0, unrelated: 1});
|
||||
});
|
||||
assertLog([]);
|
||||
expect(root).toMatchRenderedOutput('A: 0, B: 0');
|
||||
|
||||
// Now update the same a field again, but this time, also update a different
|
||||
// context in the same batch. The other context prevents a bail out.
|
||||
await act(async () => {
|
||||
setContextA({a: 0, unrelated: 1});
|
||||
setContextB(1);
|
||||
});
|
||||
assertLog(['A: 0, B: 1']);
|
||||
expect(root).toMatchRenderedOutput('A: 0, B: 1');
|
||||
});
|
||||
});
|
||||
@@ -42,7 +42,6 @@ export {
|
||||
use,
|
||||
useActionState,
|
||||
useCallback,
|
||||
unstable_useContextWithBailout,
|
||||
useContext,
|
||||
useDebugValue,
|
||||
useDeferredValue,
|
||||
|
||||
@@ -37,7 +37,6 @@ import {postpone} from './ReactPostpone';
|
||||
import {
|
||||
getCacheForType,
|
||||
useCallback,
|
||||
unstable_useContextWithBailout,
|
||||
useContext,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
@@ -86,7 +85,6 @@ export {
|
||||
cache,
|
||||
postpone as unstable_postpone,
|
||||
useCallback,
|
||||
unstable_useContextWithBailout,
|
||||
useContext,
|
||||
useEffect,
|
||||
useEffectEvent as experimental_useEffectEvent,
|
||||
|
||||
@@ -19,10 +19,6 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableContextProfiling,
|
||||
enableLazyContextPropagation,
|
||||
} from '../../shared/ReactFeatureFlags';
|
||||
|
||||
type BasicStateAction<S> = (S => S) | S;
|
||||
type Dispatch<A> = A => void;
|
||||
@@ -69,27 +65,6 @@ export function useContext<T>(Context: ReactContext<T>): T {
|
||||
return dispatcher.useContext(Context);
|
||||
}
|
||||
|
||||
export function unstable_useContextWithBailout<T>(
|
||||
context: ReactContext<T>,
|
||||
select: (T => Array<mixed>) | null,
|
||||
): T {
|
||||
if (!(enableLazyContextPropagation && enableContextProfiling)) {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
|
||||
const dispatcher = resolveDispatcher();
|
||||
if (__DEV__) {
|
||||
if (context.$$typeof === REACT_CONSUMER_TYPE) {
|
||||
console.error(
|
||||
'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
|
||||
'Did you mean to call useContext(Context) instead?',
|
||||
);
|
||||
}
|
||||
}
|
||||
// $FlowFixMe[not-a-function] This is unstable, thus optional
|
||||
return dispatcher.unstable_useContextWithBailout(context, select);
|
||||
}
|
||||
|
||||
export function useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
|
||||
@@ -101,9 +101,6 @@ export const enableTransitionTracing = false;
|
||||
|
||||
export const enableLazyContextPropagation = true;
|
||||
|
||||
// Expose unstable useContext for performance testing
|
||||
export const enableContextProfiling = false;
|
||||
|
||||
// FB-only usage. The new API has different semantics.
|
||||
export const enableLegacyHidden = false;
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ export const enableFizzExternalRuntime = true;
|
||||
export const enableGetInspectorDataForInstanceInProduction = true;
|
||||
export const enableHalt = false;
|
||||
export const enableInfiniteRenderLoopDetection = false;
|
||||
export const enableContextProfiling = false;
|
||||
export const enableLazyContextPropagation = true;
|
||||
export const enableLegacyCache = false;
|
||||
export const enableLegacyFBSupport = false;
|
||||
|
||||
@@ -43,7 +43,6 @@ export const enableHalt = false;
|
||||
export const enableHiddenSubtreeInsertionEffectCleanup = false;
|
||||
export const enableInfiniteRenderLoopDetection = false;
|
||||
export const enableLazyContextPropagation = true;
|
||||
export const enableContextProfiling = false;
|
||||
export const enableLegacyCache = false;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableLegacyHidden = false;
|
||||
|
||||
@@ -49,7 +49,6 @@ export const transitionLaneExpirationMs = 5000;
|
||||
|
||||
export const disableSchedulerTimeoutInWorkLoop = false;
|
||||
export const enableLazyContextPropagation = true;
|
||||
export const enableContextProfiling = false;
|
||||
export const enableLegacyHidden = false;
|
||||
|
||||
export const enableTransitionTracing = false;
|
||||
|
||||
@@ -51,7 +51,6 @@ export const transitionLaneExpirationMs = 5000;
|
||||
|
||||
export const disableSchedulerTimeoutInWorkLoop = false;
|
||||
export const enableLazyContextPropagation = true;
|
||||
export const enableContextProfiling = false;
|
||||
export const enableLegacyHidden = false;
|
||||
|
||||
export const enableTransitionTracing = false;
|
||||
|
||||
@@ -79,8 +79,6 @@ export const enablePostpone = false;
|
||||
|
||||
export const enableHalt = false;
|
||||
|
||||
export const enableContextProfiling = true;
|
||||
|
||||
// TODO: www currently relies on this feature. It's disabled in open source.
|
||||
// Need to remove it.
|
||||
export const disableCommentsAsDOMContainers = false;
|
||||
|
||||
@@ -530,4 +530,3 @@
|
||||
"542": "Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary.",
|
||||
"543": "Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React."
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user