Add a feature flag for new behavior

This commit is contained in:
Dan Abramov
2024-02-11 02:16:10 +00:00
parent 0b14570d80
commit c86765f818
27 changed files with 289 additions and 45 deletions
+6 -1
View File
@@ -14,11 +14,13 @@ import type {
RejectedThenable,
ReactCustomFormAction,
} from 'shared/ReactTypes';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
import {
REACT_ELEMENT_TYPE,
REACT_LAZY_TYPE,
REACT_CONTEXT_TYPE,
REACT_PROVIDER_TYPE,
getIteratorFn,
} from 'shared/ReactSymbols';
@@ -297,7 +299,10 @@ export function processReply(
'React Lazy cannot be passed to Server Functions from the Client.%s',
describeObjectForErrorMessage(parent, key),
);
} else if ((value: any).$$typeof === REACT_CONTEXT_TYPE) {
} else if (
(value: any).$$typeof ===
(enableRenderableContext ? REACT_CONTEXT_TYPE : REACT_PROVIDER_TYPE)
) {
console.error(
'React Context Providers cannot be passed to Server Functions from the Client.%s',
describeObjectForErrorMessage(parent, key),
+5 -1
View File
@@ -821,7 +821,11 @@ function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
let current: null | Fiber = fiber;
while (current) {
if (current.tag === ContextProvider) {
const context: ReactContext<any> = current.type;
let context: ReactContext<any> = current.type;
if ((context: any)._context !== undefined) {
// Support inspection of pre-19+ providers.
context = (context: any)._context;
}
if (!contextMap.has(context)) {
// Store the current value that we're going to restore later.
contextMap.set(context, context._currentValue);
@@ -296,6 +296,12 @@ describe('ReactDOMServerIntegration', () => {
});
itRenders('should treat Context as Context.Provider', async render => {
// The `itRenders` helpers don't work with the gate pragma, so we have to do
// this instead.
if (gate(flags => !flags.enableRenderableContext)) {
return;
}
const Theme = React.createContext('dark');
const Language = React.createContext('french');
@@ -1000,6 +1000,7 @@ describe('ReactDOMServer', () => {
]);
});
// @gate enableRenderableContext || !__DEV__
it('should warn if an invalid contextType is defined', () => {
const Context = React.createContext();
class ComponentA extends React.Component {
+28 -5
View File
@@ -18,12 +18,14 @@ import {
REACT_MEMO_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONSUMER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
} from 'shared/ReactSymbols';
import isValidElementType from 'shared/isValidElementType';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
export function typeOf(object: any): mixed {
if (typeof object === 'object' && object !== null) {
@@ -47,8 +49,17 @@ export function typeOf(object: any): mixed {
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
case REACT_CONSUMER_TYPE:
return $$typeofType;
case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
return $$typeofType;
}
// Fall through
case REACT_PROVIDER_TYPE:
if (!enableRenderableContext) {
return $$typeofType;
}
// Fall through
default:
return $$typeof;
}
@@ -61,8 +72,12 @@ export function typeOf(object: any): mixed {
return undefined;
}
export const ContextConsumer = REACT_CONSUMER_TYPE;
export const ContextProvider = REACT_CONTEXT_TYPE;
export const ContextConsumer: symbol = enableRenderableContext
? REACT_CONSUMER_TYPE
: REACT_CONTEXT_TYPE;
export const ContextProvider: symbol = enableRenderableContext
? REACT_CONTEXT_TYPE
: REACT_PROVIDER_TYPE;
export const Element = REACT_ELEMENT_TYPE;
export const ForwardRef = REACT_FORWARD_REF_TYPE;
export const Fragment = REACT_FRAGMENT_TYPE;
@@ -77,10 +92,18 @@ export const SuspenseList = REACT_SUSPENSE_LIST_TYPE;
export {isValidElementType};
export function isContextConsumer(object: any): boolean {
return typeOf(object) === REACT_CONSUMER_TYPE;
if (enableRenderableContext) {
return typeOf(object) === REACT_CONSUMER_TYPE;
} else {
return typeOf(object) === REACT_CONTEXT_TYPE;
}
}
export function isContextProvider(object: any): boolean {
return typeOf(object) === REACT_CONTEXT_TYPE;
if (enableRenderableContext) {
return typeOf(object) === REACT_CONTEXT_TYPE;
} else {
return typeOf(object) === REACT_PROVIDER_TYPE;
}
}
export function isElement(object: any): boolean {
return (
+20 -5
View File
@@ -38,6 +38,7 @@ import {
enableDebugTracing,
enableFloat,
enableDO_NOT_USE_disableStrictPassiveEffect,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
@@ -94,6 +95,7 @@ import {
REACT_DEBUG_TRACING_MODE_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_SUSPENSE_TYPE,
@@ -578,13 +580,26 @@ export function createFiberFromTypeAndProps(
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
if (!enableRenderableContext) {
fiberTag = ContextProvider;
break getTag;
}
// Fall through
case REACT_CONTEXT_TYPE:
fiberTag = ContextProvider;
break getTag;
if (enableRenderableContext) {
fiberTag = ContextProvider;
break getTag;
} else {
fiberTag = ContextConsumer;
break getTag;
}
case REACT_CONSUMER_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
if (enableRenderableContext) {
fiberTag = ContextConsumer;
break getTag;
}
// Fall through
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
if (__DEV__) {
+25 -5
View File
@@ -110,6 +110,7 @@ import {
enableFormActions,
enableAsyncActions,
enablePostpone,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import isArray from 'shared/isArray';
import shallowEqual from 'shared/shallowEqual';
@@ -3528,7 +3529,12 @@ function updateContextProvider(
workInProgress: Fiber,
renderLanes: Lanes,
) {
const context: ReactContext<any> = workInProgress.type;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
@@ -3590,9 +3596,18 @@ function updateContextConsumer(
workInProgress: Fiber,
renderLanes: Lanes,
) {
const consumerType: ReactConsumerType<any> = workInProgress.type;
const context = consumerType._context;
let context: ReactContext<any>;
if (enableRenderableContext) {
const consumerType: ReactConsumerType<any> = workInProgress.type;
context = consumerType._context;
} else {
context = workInProgress.type;
if (__DEV__) {
if ((context: any)._context !== undefined) {
context = (context: any)._context;
}
}
}
const newProps = workInProgress.pendingProps;
const render = newProps.children;
@@ -3838,7 +3853,12 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
const context: ReactContext<any> = workInProgress.type;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
pushProvider(workInProgress, context, newValue);
break;
}
+7 -1
View File
@@ -39,6 +39,7 @@ import {
enableCache,
enableTransitionTracing,
enableFloat,
enableRenderableContext,
passChildrenWhenCloningPersistedNodes,
} from 'shared/ReactFeatureFlags';
@@ -1505,7 +1506,12 @@ function completeWork(
return null;
case ContextProvider:
// Pop provider fiber
const context: ReactContext<any> = workInProgress.type;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
popProvider(context, workInProgress);
bubbleProperties(workInProgress);
return null;
+7 -1
View File
@@ -46,6 +46,7 @@ import {
enableLazyContextPropagation,
enableFormActions,
enableAsyncActions,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import {
getHostTransitionProvider,
@@ -561,7 +562,12 @@ function propagateParentContextChanges(
const oldProps = currentParent.memoizedProps;
if (oldProps !== null) {
const context: ReactContext<any> = parent.type;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = parent.type;
} else {
context = parent.type._context;
}
const newProps = parent.pendingProps;
const newValue = newProps.value;
+8 -2
View File
@@ -22,7 +22,10 @@ import {
import {isFiberSuspenseAndTimedOut} from './ReactFiberTreeReflection';
import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags';
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
import {
enableScopeAPI,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
@@ -113,7 +116,10 @@ function collectNearestContextValues<T>(
context: ReactContext<T>,
childContextValues: Array<T>,
): void {
if (node.tag === ContextProvider && node.type === context) {
if (
node.tag === ContextProvider &&
(enableRenderableContext ? node.type : node.type._context) === context
) {
const contextValue = node.memoizedProps.value;
childContextValues.push(contextValue);
} else {
+13 -2
View File
@@ -35,6 +35,7 @@ import {
enableProfilerTimer,
enableCache,
enableTransitionTracing,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
@@ -160,7 +161,12 @@ function unwindWork(
popHostContainer(workInProgress);
return null;
case ContextProvider:
const context: ReactContext<any> = workInProgress.type;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
popProvider(context, workInProgress);
return null;
case OffscreenComponent:
@@ -250,7 +256,12 @@ function unwindInterruptedWork(
popSuspenseListContext(interruptedWork);
break;
case ContextProvider:
const context: ReactContext<any> = interruptedWork.type;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = interruptedWork.type;
} else {
context = interruptedWork.type._context;
}
popProvider(context, interruptedWork);
break;
case OffscreenComponent:
@@ -1339,6 +1339,7 @@ describe('ReactNewContext', () => {
);
});
// @gate enableRenderableContext || !__DEV__
it('warns when passed a consumer', async () => {
const Context = React.createContext(0);
function Foo() {
@@ -1635,6 +1636,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
});
});
// @gate enableRenderableContext
it('should treat Context as Context.Provider', async () => {
const BarContext = React.createContext({value: 'bar-initial'});
expect(BarContext.Provider).toBe(BarContext);
+18 -5
View File
@@ -10,7 +10,10 @@
import type {ReactContext, ReactConsumerType} from 'shared/ReactTypes';
import type {Fiber} from './ReactInternalTypes';
import {enableLegacyHidden} from 'shared/ReactFeatureFlags';
import {
enableLegacyHidden,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
@@ -68,11 +71,21 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null {
case CacheComponent:
return 'Cache';
case ContextConsumer:
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
if (enableRenderableContext) {
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
} else {
const context: ReactContext<any> = (type: any);
return getContextName(context) + '.Consumer';
}
case ContextProvider:
const context: ReactContext<any> = (type: any);
return getContextName(context) + '.Provider';
if (enableRenderableContext) {
const context: ReactContext<any> = (type: any);
return getContextName(context) + '.Provider';
} else {
const provider = (type: any);
return getContextName(provider._context) + '.Provider';
}
case DehydratedFragment:
return 'DehydratedFragment';
case ForwardRef:
+32 -6
View File
@@ -32,6 +32,7 @@ import type {ContextSnapshot} from './ReactFizzNewContext';
import type {ComponentStackNode} from './ReactFizzComponentStack';
import type {TreeContext} from './ReactFizzTreeContext';
import type {ThenableState} from './ReactFizzThenable';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
import {
scheduleWork,
@@ -127,6 +128,7 @@ import {
REACT_FRAGMENT_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_SCOPE_TYPE,
@@ -1699,10 +1701,9 @@ function renderContextConsumer(
request: Request,
task: Task,
keyPath: KeyNode,
type: ReactConsumerType<any>,
context: ReactContext<any>,
props: Object,
): void {
const context = type._context;
const render = props.children;
if (__DEV__) {
@@ -1882,13 +1883,38 @@ function renderElement(
renderMemo(request, task, keyPath, type, props, ref);
return;
}
case REACT_PROVIDER_TYPE: {
if (!enableRenderableContext) {
const context: ReactContext<any> = (type: any)._context;
renderContextProvider(request, task, keyPath, context, props);
return;
}
// Fall through
}
case REACT_CONTEXT_TYPE: {
renderContextProvider(request, task, keyPath, type, props);
return;
if (enableRenderableContext) {
const context = type;
renderContextProvider(request, task, keyPath, context, props);
return;
} else {
let context: ReactContext<any> = (type: any);
if (__DEV__) {
if ((context: any)._context !== undefined) {
context = (context: any)._context;
}
}
renderContextConsumer(request, task, keyPath, context, props);
return;
}
}
case REACT_CONSUMER_TYPE: {
renderContextConsumer(request, task, keyPath, type, props);
return;
if (enableRenderableContext) {
const context: ReactContext<any> = (type: ReactConsumerType<any>)
._context;
renderContextConsumer(request, task, keyPath, context, props);
return;
}
// Fall through
}
case REACT_LAZY_TYPE: {
renderLazyComponent(request, task, keyPath, type, props);
+72 -6
View File
@@ -7,9 +7,14 @@
* @flow
*/
import {REACT_CONSUMER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import {
REACT_PROVIDER_TYPE,
REACT_CONSUMER_TYPE,
REACT_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
import type {ReactContext} from 'shared/ReactTypes';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
export function createContext<T>(defaultValue: T): ReactContext<T> {
// TODO: Second argument used to be an optional `calculateChangedBits`
@@ -32,11 +37,72 @@ export function createContext<T>(defaultValue: T): ReactContext<T> {
Consumer: (null: any),
};
context.Provider = context;
context.Consumer = {
$$typeof: REACT_CONSUMER_TYPE,
_context: context,
};
if (enableRenderableContext) {
context.Provider = context;
context.Consumer = {
$$typeof: REACT_CONSUMER_TYPE,
_context: context,
};
} else {
(context: any).Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
if (__DEV__) {
const Consumer: any = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context,
};
Object.defineProperties(Consumer, {
Provider: {
get() {
return context.Provider;
},
set(_Provider: any) {
context.Provider = _Provider;
},
},
_currentValue: {
get() {
return context._currentValue;
},
set(_currentValue: T) {
context._currentValue = _currentValue;
},
},
_currentValue2: {
get() {
return context._currentValue2;
},
set(_currentValue2: T) {
context._currentValue2 = _currentValue2;
},
},
_threadCount: {
get() {
return context._threadCount;
},
set(_threadCount: number) {
context._threadCount = _threadCount;
},
},
Consumer: {
get() {
return context.Consumer;
},
},
displayName: {
get() {
return context.displayName;
},
set(displayName: void | string) {},
},
});
(context: any).Consumer = Consumer;
} else {
(context: any).Consumer = context;
}
}
if (__DEV__) {
context._currentRenderer = null;
@@ -564,6 +564,7 @@ describe('ReactContextValidator', () => {
);
});
// @gate enableRenderableContext || !__DEV__
it('should warn if an invalid contextType is defined', () => {
const Context = React.createContext();
class ComponentA extends React.Component {
+2
View File
@@ -121,6 +121,8 @@ export const passChildrenWhenCloningPersistedNodes = false;
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
export const enableRenderableContext = false;
/**
* Enables an expiration time for retry lanes to avoid starvation.
*/
+1
View File
@@ -17,6 +17,7 @@ export const REACT_PORTAL_TYPE: symbol = Symbol.for('react.portal');
export const REACT_FRAGMENT_TYPE: symbol = Symbol.for('react.fragment');
export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler');
export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider'); // TODO: Delete with enableRenderableContext
export const REACT_CONSUMER_TYPE: symbol = Symbol.for('react.consumer');
export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context');
export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref');
@@ -66,6 +66,7 @@ export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = true;
export const enableGetInspectorDataForInstanceInProduction = true;
export const enableRenderableContext = false;
export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
@@ -49,6 +49,7 @@ export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = true;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableRenderableContext = false;
export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
@@ -49,6 +49,7 @@ export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = true;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableRenderableContext = false;
export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
@@ -51,6 +51,7 @@ export const enableUseEffectEventHook = false;
export const enableClientRenderFallbackOnTextMismatch = true;
export const enableUseRefAccessWarning = false;
export const enableInfiniteRenderLoopDetection = false;
export const enableRenderableContext = false;
export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
@@ -49,6 +49,7 @@ export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = true;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableRenderableContext = false;
export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
@@ -29,6 +29,7 @@ export const enableFormActions = __VARIANT__;
export const alwaysThrottleRetries = __VARIANT__;
export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
export const enableUseDeferredValueInitialArg = __VARIANT__;
export const enableRenderableContext = __VARIANT__;
export const enableRetryLaneExpiration = __VARIANT__;
export const retryLaneExpirationMs = 5000;
@@ -37,6 +37,7 @@ export const {
syncLaneExpirationMs,
transitionLaneExpirationMs,
enableInfiniteRenderLoopDetection,
enableRenderableContext,
} = dynamicFeatureFlags;
// On WWW, __EXPERIMENTAL__ is used for a new modern build.
+24 -4
View File
@@ -18,6 +18,7 @@ import {
REACT_PORTAL_TYPE,
REACT_MEMO_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
@@ -26,7 +27,11 @@ import {
REACT_TRACING_MARKER_TYPE,
} from 'shared/ReactSymbols';
import {enableTransitionTracing, enableCache} from './ReactFeatureFlags';
import {
enableTransitionTracing,
enableCache,
enableRenderableContext,
} from './ReactFeatureFlags';
// Keep in sync with react-reconciler/getComponentNameFromFiber
function getWrappedName(
@@ -98,12 +103,27 @@ export default function getComponentNameFromType(type: mixed): string | null {
}
}
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
if (enableRenderableContext) {
return null;
} else {
const provider = (type: any);
return getContextName(provider._context) + '.Provider';
}
case REACT_CONTEXT_TYPE:
const context: ReactContext<any> = (type: any);
return getContextName(context) + '.Provider';
if (enableRenderableContext) {
return getContextName(context) + '.Provider';
} else {
return getContextName(context) + '.Consumer';
}
case REACT_CONSUMER_TYPE:
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
if (enableRenderableContext) {
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
} else {
return null;
}
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, 'ForwardRef');
case REACT_MEMO_TYPE:
+4 -1
View File
@@ -10,6 +10,7 @@
import {
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_PROVIDER_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PROFILER_TYPE,
@@ -31,6 +32,7 @@ import {
enableTransitionTracing,
enableDebugTracing,
enableLegacyHidden,
enableRenderableContext,
} from './ReactFeatureFlags';
const REACT_CLIENT_REFERENCE: symbol = Symbol.for('react.client.reference');
@@ -62,7 +64,8 @@ export default function isValidElementType(type: mixed): boolean {
type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_CONSUMER_TYPE ||
(!enableRenderableContext && type.$$typeof === REACT_PROVIDER_TYPE) ||
(enableRenderableContext && type.$$typeof === REACT_CONSUMER_TYPE) ||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
// This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since