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,
|
||||
|
||||
// The ART renderer is secondary to the React DOM renderer.
|
||||
isPrimaryRenderer: false,
|
||||
|
||||
mutation: {
|
||||
appendChild(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
|
|
|
|||
|
|
@ -339,6 +339,60 @@ describe('ReactART', () => {
|
|||
doClick(instance);
|
||||
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', () => {
|
||||
|
|
|
|||
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,
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
|
||||
mutation: {
|
||||
commitMount(
|
||||
domElement: Instance,
|
||||
|
|
|
|||
|
|
@ -217,6 +217,9 @@ const ReactFabricRenderer = ReactFiberReconciler({
|
|||
|
||||
now: ReactNativeFrameScheduling.now,
|
||||
|
||||
// The Fabric renderer is secondary to the existing React Native renderer.
|
||||
isPrimaryRenderer: false,
|
||||
|
||||
prepareForCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
|
|
|||
|
|
@ -169,6 +169,8 @@ const NativeRenderer = ReactFiberReconciler({
|
|||
|
||||
now: ReactNativeFrameScheduling.now,
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
|
||||
prepareForCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
|
|
|||
|
|
@ -185,6 +185,8 @@ let SharedHostConfig = {
|
|||
now(): number {
|
||||
return elapsedTimeInMs;
|
||||
},
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
};
|
||||
|
||||
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 {pushProvider} = newContext;
|
||||
const {
|
||||
pushProvider,
|
||||
getContextCurrentValue,
|
||||
getContextChangedBits,
|
||||
} = newContext;
|
||||
|
||||
const {
|
||||
markActualRenderTimeStarted,
|
||||
|
|
@ -1048,8 +1052,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
|||
const newProps = workInProgress.pendingProps;
|
||||
const oldProps = workInProgress.memoizedProps;
|
||||
|
||||
const newValue = context._currentValue;
|
||||
const changedBits = context._changedBits;
|
||||
const newValue = getContextCurrentValue(context);
|
||||
const changedBits = getContextChangedBits(context);
|
||||
|
||||
if (hasLegacyContextChanged()) {
|
||||
// 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 {StackCursor, Stack} from './ReactFiberStack';
|
||||
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
export type NewContext = {
|
||||
pushProvider(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 providerCursor: StackCursor<Fiber | null> = createCursor(null);
|
||||
|
|
@ -34,21 +36,38 @@ export default function(stack: Stack) {
|
|||
function pushProvider(providerFiber: Fiber): void {
|
||||
const context: ReactContext<any> = providerFiber.type._context;
|
||||
|
||||
push(changedBitsCursor, context._changedBits, providerFiber);
|
||||
push(valueCursor, context._currentValue, providerFiber);
|
||||
push(providerCursor, providerFiber, providerFiber);
|
||||
if (isPrimaryRenderer) {
|
||||
push(changedBitsCursor, context._changedBits, providerFiber);
|
||||
push(valueCursor, context._currentValue, providerFiber);
|
||||
push(providerCursor, providerFiber, providerFiber);
|
||||
|
||||
context._currentValue = providerFiber.pendingProps.value;
|
||||
context._changedBits = providerFiber.stateNode;
|
||||
context._currentValue = providerFiber.pendingProps.value;
|
||||
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__) {
|
||||
warning(
|
||||
context._currentRenderer === null ||
|
||||
context._currentRenderer === rendererSigil,
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same context provider. This is currently unsupported.',
|
||||
);
|
||||
context._currentRenderer = rendererSigil;
|
||||
context._currentValue2 = providerFiber.pendingProps.value;
|
||||
context._changedBits2 = providerFiber.stateNode;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
context._currentRenderer2 === null ||
|
||||
context._currentRenderer2 === rendererSigil,
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same context provider. This is currently unsupported.',
|
||||
);
|
||||
context._currentRenderer2 = rendererSigil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,12 +80,27 @@ export default function(stack: Stack) {
|
|||
pop(changedBitsCursor, providerFiber);
|
||||
|
||||
const context: ReactContext<any> = providerFiber.type._context;
|
||||
context._currentValue = currentValue;
|
||||
context._changedBits = changedBits;
|
||||
if (isPrimaryRenderer) {
|
||||
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 {
|
||||
pushProvider,
|
||||
popProvider,
|
||||
getContextCurrentValue,
|
||||
getContextChangedBits,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ export type HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL> = {
|
|||
|
||||
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>,
|
||||
|
||||
+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 hostContext = ReactFiberHostContext(config, stack);
|
||||
const legacyContext = ReactFiberLegacyContext(stack);
|
||||
const newContext = ReactFiberNewContext(stack);
|
||||
const newContext = ReactFiberNewContext(stack, config.isPrimaryRenderer);
|
||||
const profilerTimer = createProfilerTimer(now);
|
||||
const {popHostContext, popHostContainer} = hostContext;
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -226,6 +226,8 @@ const TestRenderer = ReactFiberReconciler({
|
|||
// Even after the reconciler has initialized and read host config values.
|
||||
now: () => nowImplementation(),
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
|
||||
mutation: {
|
||||
commitUpdate(
|
||||
instance: Instance,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,14 @@ export function createContext<T>(
|
|||
_calculateChangedBits: calculateChangedBits,
|
||||
_defaultValue: 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,
|
||||
_changedBits2: 0,
|
||||
// These are circular
|
||||
Provider: (null: any),
|
||||
Consumer: (null: any),
|
||||
|
|
@ -50,6 +57,7 @@ export function createContext<T>(
|
|||
|
||||
if (__DEV__) {
|
||||
context._currentRenderer = null;
|
||||
context._currentRenderer2 = null;
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
|
|||
|
|
@ -85,10 +85,13 @@ export type ReactContext<T> = {
|
|||
_defaultValue: T,
|
||||
|
||||
_currentValue: T,
|
||||
_currentValue2: T,
|
||||
_changedBits: number,
|
||||
_changedBits2: number,
|
||||
|
||||
// DEV only
|
||||
_currentRenderer?: Object | null,
|
||||
_currentRenderer2?: Object | null,
|
||||
};
|
||||
|
||||
export type ReactPortal = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user