Skip to content

Commit

Permalink
Add unstable_concurrentUpdatesByDefault (#21227)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickhanlonii authored Apr 28, 2021
1 parent 86f3385 commit 9e9dac6
Show file tree
Hide file tree
Showing 24 changed files with 159 additions and 38 deletions.
35 changes: 35 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMRoot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let React = require('react');
let ReactDOM = require('react-dom');
let ReactDOMServer = require('react-dom/server');
let Scheduler = require('scheduler');
let act;

describe('ReactDOMRoot', () => {
let container;
Expand All @@ -24,6 +25,7 @@ describe('ReactDOMRoot', () => {
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
act = require('react-dom/test-utils').unstable_concurrentAct;
});

if (!__EXPERIMENTAL__) {
Expand Down Expand Up @@ -316,4 +318,37 @@ describe('ReactDOMRoot', () => {
{withoutStack: true},
);
});

// @gate experimental
it('opts-in to concurrent default updates', async () => {
const root = ReactDOM.unstable_createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});

function Foo({value}) {
Scheduler.unstable_yieldValue(value);
return <div>{value}</div>;
}

await act(async () => {
root.render(<Foo value="a" />);
});

expect(container.textContent).toEqual('a');

await act(async () => {
root.render(<Foo value="b" />);

expect(Scheduler).toHaveYielded(['a']);
expect(container.textContent).toEqual('a');

expect(Scheduler).toFlushAndYieldThrough(['b']);
if (gate(flags => flags.allowConcurrentByDefault)) {
expect(container.textContent).toEqual('a');
} else {
expect(container.textContent).toEqual('b');
}
});
expect(container.textContent).toEqual('b');
});
});
11 changes: 11 additions & 0 deletions packages/react-dom/src/client/ReactDOMRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type RootOptions = {
...
},
unstable_strictModeLevel?: number,
unstable_concurrentUpdatesByDefault?: boolean,
...
};

Expand All @@ -52,6 +53,7 @@ import {
} from 'react-reconciler/src/ReactFiberReconciler';
import invariant from 'shared/invariant';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';

function ReactDOMRoot(container: Container, options: void | RootOptions) {
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
Expand Down Expand Up @@ -126,12 +128,21 @@ function createRootImpl(
? options.unstable_strictModeLevel
: null;

let concurrentUpdatesByDefaultOverride = null;
if (allowConcurrentByDefault) {
concurrentUpdatesByDefaultOverride =
options != null && options.unstable_concurrentUpdatesByDefault != null
? options.unstable_concurrentUpdatesByDefault
: null;
}

const root = createContainer(
container,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
markContainerAsRoot(root.current, container);

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-renderer/src/ReactFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function render(
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, LegacyRoot, false, null, null);
root = createContainer(containerTag, LegacyRoot, false, null, null, null);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ function render(
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, LegacyRoot, false, null, null);
root = createContainer(containerTag, LegacyRoot, false, null, null, null);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
Expand Down
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
enableStrictEffects,
enableProfilerTimer,
enableScopeAPI,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
Expand Down Expand Up @@ -68,6 +70,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
Expand Down Expand Up @@ -420,6 +423,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
Expand All @@ -440,6 +444,15 @@ export function createHostRootFiber(
mode |= StrictLegacyMode;
}
}
if (
// We only use this flag for our repo tests to check both behaviors.
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
!enableSyncDefaultUpdates ||
// Only for internal experiments.
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode;
}
} else {
mode = NoMode;
}
Expand Down
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
enableStrictEffects,
enableProfilerTimer,
enableScopeAPI,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
Expand Down Expand Up @@ -68,6 +70,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
Expand Down Expand Up @@ -420,6 +423,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
Expand All @@ -440,6 +444,15 @@ export function createHostRootFiber(
mode |= StrictLegacyMode;
}
}
if (
// We only use this flag for our repo tests to check both behaviors.
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
!enableSyncDefaultUpdates ||
// Only for internal experiments.
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode;
}
} else {
mode = NoMode;
}
Expand Down
34 changes: 20 additions & 14 deletions packages/react-reconciler/src/ReactFiberLane.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';

export const SyncLanePriority: LanePriority = 12;

Expand Down Expand Up @@ -318,11 +319,12 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}

if (
// TODO: Check for root override, once that lands
enableSyncDefaultUpdates &&
(nextLanes & InputContinuousLane) !== NoLanes
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// When updates are sync by default, we entangle continous priority updates
// Do nothing, use the lanes as they were assigned.
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
// When updates are sync by default, we entangle continuous priority updates
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
Expand Down Expand Up @@ -527,17 +529,21 @@ export function shouldTimeSlice(root: FiberRoot, lanes: Lanes) {
// finish rendering without yielding execution.
return false;
}
if (enableSyncDefaultUpdates) {
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
// TODO: Check for root override, once that lands
return (lanes & SyncDefaultLanes) === NoLanes;
} else {

if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
return true;
}

const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
return (lanes & SyncDefaultLanes) === NoLanes;
}

export function isTransitionLane(lane: Lane) {
Expand Down
34 changes: 20 additions & 14 deletions packages/react-reconciler/src/ReactFiberLane.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';

export const SyncLanePriority: LanePriority = 12;

Expand Down Expand Up @@ -318,11 +319,12 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}

if (
// TODO: Check for root override, once that lands
enableSyncDefaultUpdates &&
(nextLanes & InputContinuousLane) !== NoLanes
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// When updates are sync by default, we entangle continous priority updates
// Do nothing, use the lanes as they were assigned.
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
// When updates are sync by default, we entangle continuous priority updates
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
Expand Down Expand Up @@ -527,17 +529,21 @@ export function shouldTimeSlice(root: FiberRoot, lanes: Lanes) {
// finish rendering without yielding execution.
return false;
}
if (enableSyncDefaultUpdates) {
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
// TODO: Check for root override, once that lands
return (lanes & SyncDefaultLanes) === NoLanes;
} else {

if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
return true;
}

const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
return (lanes & SyncDefaultLanes) === NoLanes;
}

export function isTransitionLane(lane: Lane) {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,15 @@ export function createContainer(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,15 @@ export function createContainer(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
}

Expand Down
7 changes: 6 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function createFiberRoot(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
Expand All @@ -107,7 +108,11 @@ export function createFiberRoot(

// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
const uninitializedFiber = createHostRootFiber(
tag,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

Expand Down
7 changes: 6 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function createFiberRoot(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
Expand All @@ -107,7 +108,11 @@ export function createFiberRoot(

// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
const uninitializedFiber = createHostRootFiber(
tag,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

Expand Down
Loading

0 comments on commit 9e9dac6

Please sign in to comment.