mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Support sharing context objects between concurrent renderers (#12779)
* Support concurrent primary and secondary renderers. As a workaround to support multiple concurrent renderers, we categorize some renderers as primary and others as secondary. We only expect there to be two concurrent renderers at most: React Native (primary) and Fabric (secondary); React DOM (primary) and React ART (secondary). Secondary renderers store their context values on separate fields. * Add back concurrent renderer warning Only warn for two concurrent primary or two concurrent secondary renderers. * Change "_secondary" suffix to "2" #EveryBitCounts
This commit is contained in:
parent
6565795377
commit
b0726e9947
3
packages/react-art/src/ReactART.js
vendored
3
packages/react-art/src/ReactART.js
vendored
|
|
@ -478,6 +478,9 @@ const ARTRenderer = ReactFiberReconciler({
|
||||||
|
|
||||||
now: ReactScheduler.now,
|
now: ReactScheduler.now,
|
||||||
|
|
||||||
|
// The ART renderer is secondary to the React DOM renderer.
|
||||||
|
isPrimaryRenderer: false,
|
||||||
|
|
||||||
mutation: {
|
mutation: {
|
||||||
appendChild(parentInstance, child) {
|
appendChild(parentInstance, child) {
|
||||||
if (child.parentNode === parentInstance) {
|
if (child.parentNode === parentInstance) {
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,60 @@ describe('ReactART', () => {
|
||||||
doClick(instance);
|
doClick(instance);
|
||||||
expect(onClick2).toBeCalled();
|
expect(onClick2).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can concurrently render with a "primary" renderer while sharing context', () => {
|
||||||
|
const CurrentRendererContext = React.createContext(null);
|
||||||
|
|
||||||
|
function Yield(props) {
|
||||||
|
testRenderer.unstable_yield(props.value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ops = [];
|
||||||
|
function LogCurrentRenderer() {
|
||||||
|
return (
|
||||||
|
<CurrentRendererContext.Consumer>
|
||||||
|
{currentRenderer => {
|
||||||
|
ops.push(currentRenderer);
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</CurrentRendererContext.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using test renderer instead of the DOM renderer here because async
|
||||||
|
// testing APIs for the DOM renderer don't exist.
|
||||||
|
const testRenderer = renderer.create(
|
||||||
|
<CurrentRendererContext.Provider value="Test">
|
||||||
|
<Yield value="A" />
|
||||||
|
<Yield value="B" />
|
||||||
|
<LogCurrentRenderer />
|
||||||
|
<Yield value="C" />
|
||||||
|
</CurrentRendererContext.Provider>,
|
||||||
|
{
|
||||||
|
unstable_isAsync: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testRenderer.unstable_flushThrough(['A']);
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Surface>
|
||||||
|
<LogCurrentRenderer />
|
||||||
|
<CurrentRendererContext.Provider value="ART">
|
||||||
|
<LogCurrentRenderer />
|
||||||
|
</CurrentRendererContext.Provider>
|
||||||
|
</Surface>,
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ops).toEqual([null, 'ART']);
|
||||||
|
|
||||||
|
ops = [];
|
||||||
|
expect(testRenderer.unstable_flushAll()).toEqual(['B', 'C']);
|
||||||
|
|
||||||
|
expect(ops).toEqual(['Test']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ReactARTComponents', () => {
|
describe('ReactARTComponents', () => {
|
||||||
|
|
|
||||||
2
packages/react-dom/src/client/ReactDOM.js
vendored
2
packages/react-dom/src/client/ReactDOM.js
vendored
|
|
@ -690,6 +690,8 @@ const DOMRenderer = ReactFiberReconciler({
|
||||||
|
|
||||||
now: ReactScheduler.now,
|
now: ReactScheduler.now,
|
||||||
|
|
||||||
|
isPrimaryRenderer: true,
|
||||||
|
|
||||||
mutation: {
|
mutation: {
|
||||||
commitMount(
|
commitMount(
|
||||||
domElement: Instance,
|
domElement: Instance,
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,9 @@ const ReactFabricRenderer = ReactFiberReconciler({
|
||||||
|
|
||||||
now: ReactNativeFrameScheduling.now,
|
now: ReactNativeFrameScheduling.now,
|
||||||
|
|
||||||
|
// The Fabric renderer is secondary to the existing React Native renderer.
|
||||||
|
isPrimaryRenderer: false,
|
||||||
|
|
||||||
prepareForCommit(): void {
|
prepareForCommit(): void {
|
||||||
// Noop
|
// Noop
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,8 @@ const NativeRenderer = ReactFiberReconciler({
|
||||||
|
|
||||||
now: ReactNativeFrameScheduling.now,
|
now: ReactNativeFrameScheduling.now,
|
||||||
|
|
||||||
|
isPrimaryRenderer: true,
|
||||||
|
|
||||||
prepareForCommit(): void {
|
prepareForCommit(): void {
|
||||||
// Noop
|
// Noop
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,8 @@ let SharedHostConfig = {
|
||||||
now(): number {
|
now(): number {
|
||||||
return elapsedTimeInMs;
|
return elapsedTimeInMs;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isPrimaryRenderer: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NoopRenderer = ReactFiberReconciler({
|
const NoopRenderer = ReactFiberReconciler({
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||||
|
|
||||||
const {pushHostContext, pushHostContainer} = hostContext;
|
const {pushHostContext, pushHostContainer} = hostContext;
|
||||||
|
|
||||||
const {pushProvider} = newContext;
|
const {
|
||||||
|
pushProvider,
|
||||||
|
getContextCurrentValue,
|
||||||
|
getContextChangedBits,
|
||||||
|
} = newContext;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
markActualRenderTimeStarted,
|
markActualRenderTimeStarted,
|
||||||
|
|
@ -1048,8 +1052,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||||
const newProps = workInProgress.pendingProps;
|
const newProps = workInProgress.pendingProps;
|
||||||
const oldProps = workInProgress.memoizedProps;
|
const oldProps = workInProgress.memoizedProps;
|
||||||
|
|
||||||
const newValue = context._currentValue;
|
const newValue = getContextCurrentValue(context);
|
||||||
const changedBits = context._changedBits;
|
const changedBits = getContextChangedBits(context);
|
||||||
|
|
||||||
if (hasLegacyContextChanged()) {
|
if (hasLegacyContextChanged()) {
|
||||||
// Normally we can bail out on props equality but if context has changed
|
// Normally we can bail out on props equality but if context has changed
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,16 @@ import type {Fiber} from './ReactFiber';
|
||||||
import type {ReactContext} from 'shared/ReactTypes';
|
import type {ReactContext} from 'shared/ReactTypes';
|
||||||
import type {StackCursor, Stack} from './ReactFiberStack';
|
import type {StackCursor, Stack} from './ReactFiberStack';
|
||||||
|
|
||||||
import warning from 'fbjs/lib/warning';
|
|
||||||
|
|
||||||
export type NewContext = {
|
export type NewContext = {
|
||||||
pushProvider(providerFiber: Fiber): void,
|
pushProvider(providerFiber: Fiber): void,
|
||||||
popProvider(providerFiber: Fiber): void,
|
popProvider(providerFiber: Fiber): void,
|
||||||
|
getContextCurrentValue(context: ReactContext<any>): any,
|
||||||
|
getContextChangedBits(context: ReactContext<any>): number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function(stack: Stack) {
|
import warning from 'fbjs/lib/warning';
|
||||||
|
|
||||||
|
export default function(stack: Stack, isPrimaryRenderer: boolean) {
|
||||||
const {createCursor, push, pop} = stack;
|
const {createCursor, push, pop} = stack;
|
||||||
|
|
||||||
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
|
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
|
||||||
|
|
@ -34,21 +36,38 @@ export default function(stack: Stack) {
|
||||||
function pushProvider(providerFiber: Fiber): void {
|
function pushProvider(providerFiber: Fiber): void {
|
||||||
const context: ReactContext<any> = providerFiber.type._context;
|
const context: ReactContext<any> = providerFiber.type._context;
|
||||||
|
|
||||||
push(changedBitsCursor, context._changedBits, providerFiber);
|
if (isPrimaryRenderer) {
|
||||||
push(valueCursor, context._currentValue, providerFiber);
|
push(changedBitsCursor, context._changedBits, providerFiber);
|
||||||
push(providerCursor, providerFiber, providerFiber);
|
push(valueCursor, context._currentValue, providerFiber);
|
||||||
|
push(providerCursor, providerFiber, providerFiber);
|
||||||
|
|
||||||
context._currentValue = providerFiber.pendingProps.value;
|
context._currentValue = providerFiber.pendingProps.value;
|
||||||
context._changedBits = providerFiber.stateNode;
|
context._changedBits = providerFiber.stateNode;
|
||||||
|
if (__DEV__) {
|
||||||
|
warning(
|
||||||
|
context._currentRenderer === null ||
|
||||||
|
context._currentRenderer === rendererSigil,
|
||||||
|
'Detected multiple renderers concurrently rendering the ' +
|
||||||
|
'same context provider. This is currently unsupported.',
|
||||||
|
);
|
||||||
|
context._currentRenderer = rendererSigil;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
push(changedBitsCursor, context._changedBits2, providerFiber);
|
||||||
|
push(valueCursor, context._currentValue2, providerFiber);
|
||||||
|
push(providerCursor, providerFiber, providerFiber);
|
||||||
|
|
||||||
if (__DEV__) {
|
context._currentValue2 = providerFiber.pendingProps.value;
|
||||||
warning(
|
context._changedBits2 = providerFiber.stateNode;
|
||||||
context._currentRenderer === null ||
|
if (__DEV__) {
|
||||||
context._currentRenderer === rendererSigil,
|
warning(
|
||||||
'Detected multiple renderers concurrently rendering the ' +
|
context._currentRenderer2 === null ||
|
||||||
'same context provider. This is currently unsupported.',
|
context._currentRenderer2 === rendererSigil,
|
||||||
);
|
'Detected multiple renderers concurrently rendering the ' +
|
||||||
context._currentRenderer = rendererSigil;
|
'same context provider. This is currently unsupported.',
|
||||||
|
);
|
||||||
|
context._currentRenderer2 = rendererSigil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,12 +80,27 @@ export default function(stack: Stack) {
|
||||||
pop(changedBitsCursor, providerFiber);
|
pop(changedBitsCursor, providerFiber);
|
||||||
|
|
||||||
const context: ReactContext<any> = providerFiber.type._context;
|
const context: ReactContext<any> = providerFiber.type._context;
|
||||||
context._currentValue = currentValue;
|
if (isPrimaryRenderer) {
|
||||||
context._changedBits = changedBits;
|
context._currentValue = currentValue;
|
||||||
|
context._changedBits = changedBits;
|
||||||
|
} else {
|
||||||
|
context._currentValue2 = currentValue;
|
||||||
|
context._changedBits2 = changedBits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContextCurrentValue(context: ReactContext<any>): any {
|
||||||
|
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContextChangedBits(context: ReactContext<any>): number {
|
||||||
|
return isPrimaryRenderer ? context._changedBits : context._changedBits2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pushProvider,
|
pushProvider,
|
||||||
popProvider,
|
popProvider,
|
||||||
|
getContextCurrentValue,
|
||||||
|
getContextChangedBits,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,11 @@ export type HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL> = {
|
||||||
|
|
||||||
now(): number,
|
now(): number,
|
||||||
|
|
||||||
|
// Temporary workaround for scenario where multiple renderers concurrently
|
||||||
|
// render using the same context objects. E.g. React DOM and React ART on the
|
||||||
|
// same page. DOM is the primary renderer; ART is the secondary renderer.
|
||||||
|
isPrimaryRenderer: boolean,
|
||||||
|
|
||||||
+hydration?: HydrationHostConfig<T, P, I, TI, HI, C, CX, PL>,
|
+hydration?: HydrationHostConfig<T, P, I, TI, HI, C, CX, PL>,
|
||||||
|
|
||||||
+mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,
|
+mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||||
const stack = ReactFiberStack();
|
const stack = ReactFiberStack();
|
||||||
const hostContext = ReactFiberHostContext(config, stack);
|
const hostContext = ReactFiberHostContext(config, stack);
|
||||||
const legacyContext = ReactFiberLegacyContext(stack);
|
const legacyContext = ReactFiberLegacyContext(stack);
|
||||||
const newContext = ReactFiberNewContext(stack);
|
const newContext = ReactFiberNewContext(stack, config.isPrimaryRenderer);
|
||||||
const profilerTimer = createProfilerTimer(now);
|
const profilerTimer = createProfilerTimer(now);
|
||||||
const {popHostContext, popHostContainer} = hostContext;
|
const {popHostContext, popHostContainer} = hostContext;
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,8 @@ const TestRenderer = ReactFiberReconciler({
|
||||||
// Even after the reconciler has initialized and read host config values.
|
// Even after the reconciler has initialized and read host config values.
|
||||||
now: () => nowImplementation(),
|
now: () => nowImplementation(),
|
||||||
|
|
||||||
|
isPrimaryRenderer: true,
|
||||||
|
|
||||||
mutation: {
|
mutation: {
|
||||||
commitUpdate(
|
commitUpdate(
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,14 @@ export function createContext<T>(
|
||||||
_calculateChangedBits: calculateChangedBits,
|
_calculateChangedBits: calculateChangedBits,
|
||||||
_defaultValue: defaultValue,
|
_defaultValue: defaultValue,
|
||||||
_currentValue: defaultValue,
|
_currentValue: defaultValue,
|
||||||
|
// As a workaround to support multiple concurrent renderers, we categorize
|
||||||
|
// some renderers as primary and others as secondary. We only expect
|
||||||
|
// there to be two concurrent renderers at most: React Native (primary) and
|
||||||
|
// Fabric (secondary); React DOM (primary) and React ART (secondary).
|
||||||
|
// Secondary renderers store their context values on separate fields.
|
||||||
|
_currentValue2: defaultValue,
|
||||||
_changedBits: 0,
|
_changedBits: 0,
|
||||||
|
_changedBits2: 0,
|
||||||
// These are circular
|
// These are circular
|
||||||
Provider: (null: any),
|
Provider: (null: any),
|
||||||
Consumer: (null: any),
|
Consumer: (null: any),
|
||||||
|
|
@ -50,6 +57,7 @@ export function createContext<T>(
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
context._currentRenderer = null;
|
context._currentRenderer = null;
|
||||||
|
context._currentRenderer2 = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,13 @@ export type ReactContext<T> = {
|
||||||
_defaultValue: T,
|
_defaultValue: T,
|
||||||
|
|
||||||
_currentValue: T,
|
_currentValue: T,
|
||||||
|
_currentValue2: T,
|
||||||
_changedBits: number,
|
_changedBits: number,
|
||||||
|
_changedBits2: number,
|
||||||
|
|
||||||
// DEV only
|
// DEV only
|
||||||
_currentRenderer?: Object | null,
|
_currentRenderer?: Object | null,
|
||||||
|
_currentRenderer2?: Object | null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactPortal = {
|
export type ReactPortal = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user