Skip to content

Commit 1e3b619

Browse files
authored
Import Scheduler directly, not via host config (#14984)
* Import Scheduler directly, not via host config We currently schedule asynchronous tasks via the host config. (The host config is a static/build-time dependency injection system that varies across different renderers — DOM, native, test, and so on.) Instead of calling platform APIs like `requestIdleCallback` directly, each renderer implements a method called `scheduleDeferredCallback`. We've since discovered that when scheduling tasks, it's crucial that React work is placed in the same queue as other, non-React work on the main thread. Otherwise, you easily end up in a starvation scenario where rendering is constantly interrupted by less important tasks. You need a centralized coordinator that is used both by React and by other frameworks and application code. This coordinator must also have a consistent API across all the different host environments, for convention's sake and so product code is portable — e.g. so the same component can work in both React Native and React Native Web. This turned into the Scheduler package. We will have different builds of Scheduler for each of our target platforms. With this approach, we treat Scheduler like a built-in platform primitive that exists wherever React is supported. Now that we have this consistent interface, the indirection of the host config no longer makes sense for the purpose of scheduling tasks. In fact, we explicitly do not want renderers to scheduled task via any system except the Scheduler package. So, this PR removes `scheduleDeferredCallback` and its associated methods from the host config in favor of directly importing Scheduler. * Missed an extraneous export
1 parent 5d49daf commit 1e3b619

File tree

11 files changed

+24
-123
lines changed

11 files changed

+24
-123
lines changed

packages/react-art/src/ReactARTHostConfig.js

-2
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,6 @@ export function getChildHostContext() {
343343
export const scheduleTimeout = setTimeout;
344344
export const cancelTimeout = clearTimeout;
345345
export const noTimeout = -1;
346-
export const schedulePassiveEffects = scheduleDeferredCallback;
347-
export const cancelPassiveEffects = cancelDeferredCallback;
348346

349347
export function shouldSetTextContent(type, props) {
350348
return (

packages/react-dom/src/client/ReactDOMHostConfig.js

-2
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,6 @@ export const scheduleTimeout =
310310
export const cancelTimeout =
311311
typeof clearTimeout === 'function' ? clearTimeout : (undefined: any);
312312
export const noTimeout = -1;
313-
export const schedulePassiveEffects = scheduleDeferredCallback;
314-
export const cancelPassiveEffects = cancelDeferredCallback;
315313

316314
// -------------------
317315
// Mutation

packages/react-native-renderer/src/ReactFabricHostConfig.js

-12
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@ import {
2020
warnForStyleProps,
2121
} from './NativeMethodsMixinUtils';
2222
import {create, diff} from './ReactNativeAttributePayload';
23-
import {
24-
now as ReactNativeFrameSchedulingNow,
25-
cancelDeferredCallback as ReactNativeFrameSchedulingCancelDeferredCallback,
26-
scheduleDeferredCallback as ReactNativeFrameSchedulingScheduleDeferredCallback,
27-
shouldYield as ReactNativeFrameSchedulingShouldYield,
28-
} from './ReactNativeFrameScheduling';
2923
import {get as getViewConfigForType} from 'ReactNativeViewConfigRegistry';
3024

3125
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
@@ -333,16 +327,10 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
333327

334328
// The Fabric renderer is secondary to the existing React Native renderer.
335329
export const isPrimaryRenderer = false;
336-
export const now = ReactNativeFrameSchedulingNow;
337-
export const scheduleDeferredCallback = ReactNativeFrameSchedulingScheduleDeferredCallback;
338-
export const cancelDeferredCallback = ReactNativeFrameSchedulingCancelDeferredCallback;
339-
export const shouldYield = ReactNativeFrameSchedulingShouldYield;
340330

341331
export const scheduleTimeout = setTimeout;
342332
export const cancelTimeout = clearTimeout;
343333
export const noTimeout = -1;
344-
export const schedulePassiveEffects = scheduleDeferredCallback;
345-
export const cancelPassiveEffects = cancelDeferredCallback;
346334

347335
// -------------------
348336
// Persistence

packages/react-native-renderer/src/ReactNativeFrameScheduling.js

-56
This file was deleted.

packages/react-native-renderer/src/ReactNativeHostConfig.js

-12
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@ import {
2323
updateFiberProps,
2424
} from './ReactNativeComponentTree';
2525
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
26-
import {
27-
now as ReactNativeFrameSchedulingNow,
28-
cancelDeferredCallback as ReactNativeFrameSchedulingCancelDeferredCallback,
29-
scheduleDeferredCallback as ReactNativeFrameSchedulingScheduleDeferredCallback,
30-
shouldYield as ReactNativeFrameSchedulingShouldYield,
31-
} from './ReactNativeFrameScheduling';
3226

3327
export type Type = string;
3428
export type Props = Object;
@@ -234,17 +228,11 @@ export function resetAfterCommit(containerInfo: Container): void {
234228
// Noop
235229
}
236230

237-
export const now = ReactNativeFrameSchedulingNow;
238231
export const isPrimaryRenderer = true;
239-
export const scheduleDeferredCallback = ReactNativeFrameSchedulingScheduleDeferredCallback;
240-
export const cancelDeferredCallback = ReactNativeFrameSchedulingCancelDeferredCallback;
241-
export const shouldYield = ReactNativeFrameSchedulingShouldYield;
242232

243233
export const scheduleTimeout = setTimeout;
244234
export const cancelTimeout = clearTimeout;
245235
export const noTimeout = -1;
246-
export const schedulePassiveEffects = scheduleDeferredCallback;
247-
export const cancelPassiveEffects = cancelDeferredCallback;
248236

249237
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
250238
return false;

packages/react-noop-renderer/src/createReactNoop.js

-8
Original file line numberDiff line numberDiff line change
@@ -304,18 +304,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
304304
return inst;
305305
},
306306

307-
scheduleDeferredCallback: Scheduler.unstable_scheduleCallback,
308-
cancelDeferredCallback: Scheduler.unstable_cancelCallback,
309-
310-
shouldYield: Scheduler.unstable_shouldYield,
311-
312307
scheduleTimeout: setTimeout,
313308
cancelTimeout: clearTimeout,
314309
noTimeout: -1,
315310

316-
schedulePassiveEffects: Scheduler.unstable_scheduleCallback,
317-
cancelPassiveEffects: Scheduler.unstable_cancelCallback,
318-
319311
prepareForCommit(): void {},
320312

321313
resetAfterCommit(): void {},

packages/react-reconciler/src/ReactFiberScheduler.js

+14-13
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
1414

1515
// Intentionally not named imports because Rollup would use dynamic dispatch for
1616
// CommonJS interop named imports.
17-
// TODO: We're not using this import anymore, but I've left this here so we
18-
// don't accidentally use named imports when we add it back.
19-
// import * as Scheduler from 'scheduler';
17+
import * as Scheduler from 'scheduler';
2018
import {
2119
__interactionsRef,
2220
__subscriberRef,
@@ -78,17 +76,11 @@ import {
7876
setCurrentFiber,
7977
} from './ReactCurrentFiber';
8078
import {
81-
now,
82-
scheduleDeferredCallback,
83-
cancelDeferredCallback,
84-
shouldYield,
8579
prepareForCommit,
8680
resetAfterCommit,
8781
scheduleTimeout,
8882
cancelTimeout,
8983
noTimeout,
90-
schedulePassiveEffects,
91-
cancelPassiveEffects,
9284
} from './ReactFiberHostConfig';
9385
import {
9486
markPendingPriorityLevel,
@@ -172,6 +164,15 @@ import {
172164
} from './ReactFiberCommitWork';
173165
import {ContextOnlyDispatcher} from './ReactFiberHooks';
174166

167+
// Intentionally not named imports because Rollup would use dynamic dispatch for
168+
// CommonJS interop named imports.
169+
const {
170+
unstable_scheduleCallback: scheduleCallback,
171+
unstable_cancelCallback: cancelCallback,
172+
unstable_shouldYield: shouldYield,
173+
unstable_now: now,
174+
} = Scheduler;
175+
175176
export type Thenable = {
176177
then(resolve: () => mixed, reject?: () => mixed): mixed,
177178
};
@@ -598,7 +599,7 @@ function markLegacyErrorBoundaryAsFailed(instance: mixed) {
598599

599600
function flushPassiveEffects() {
600601
if (passiveEffectCallbackHandle !== null) {
601-
cancelPassiveEffects(passiveEffectCallbackHandle);
602+
cancelCallback(passiveEffectCallbackHandle);
602603
}
603604
if (passiveEffectCallback !== null) {
604605
// We call the scheduled callback instead of commitPassiveEffects directly
@@ -807,7 +808,7 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
807808
// here because that code is still in flux.
808809
callback = Scheduler_tracing_wrap(callback);
809810
}
810-
passiveEffectCallbackHandle = schedulePassiveEffects(callback);
811+
passiveEffectCallbackHandle = scheduleCallback(callback);
811812
passiveEffectCallback = callback;
812813
}
813814

@@ -1978,7 +1979,7 @@ function scheduleCallbackWithExpirationTime(
19781979
if (callbackID !== null) {
19791980
// Existing callback has insufficient timeout. Cancel and schedule a
19801981
// new one.
1981-
cancelDeferredCallback(callbackID);
1982+
cancelCallback(callbackID);
19821983
}
19831984
}
19841985
// The request callback timer is already running. Don't start a new one.
@@ -1990,7 +1991,7 @@ function scheduleCallbackWithExpirationTime(
19901991
const currentMs = now() - originalStartTimeMs;
19911992
const expirationTimeMs = expirationTimeToMs(expirationTime);
19921993
const timeout = expirationTimeMs - currentMs;
1993-
callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
1994+
callbackID = scheduleCallback(performAsyncWork, {timeout});
19941995
}
19951996

19961997
// For every call to renderRoot, one of onFatal, onComplete, onSuspend, and

packages/react-reconciler/src/ReactProfilerTimer.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import type {Fiber} from './ReactFiber';
1111

1212
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
1313

14-
import {now} from './ReactFiberHostConfig';
14+
// Intentionally not named imports because Rollup would use dynamic dispatch for
15+
// CommonJS interop named imports.
16+
import * as Scheduler from 'scheduler';
17+
18+
const {unstable_now: now} = Scheduler;
1519

1620
export type ProfilerTimer = {
1721
getCommitTime(): number,

packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js

-5
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,9 @@ export const shouldSetTextContent = $$$hostConfig.shouldSetTextContent;
5151
export const shouldDeprioritizeSubtree =
5252
$$$hostConfig.shouldDeprioritizeSubtree;
5353
export const createTextInstance = $$$hostConfig.createTextInstance;
54-
export const scheduleDeferredCallback = $$$hostConfig.scheduleDeferredCallback;
55-
export const cancelDeferredCallback = $$$hostConfig.cancelDeferredCallback;
56-
export const shouldYield = $$$hostConfig.shouldYield;
5754
export const scheduleTimeout = $$$hostConfig.setTimeout;
5855
export const cancelTimeout = $$$hostConfig.clearTimeout;
5956
export const noTimeout = $$$hostConfig.noTimeout;
60-
export const schedulePassiveEffects = $$$hostConfig.schedulePassiveEffects;
61-
export const cancelPassiveEffects = $$$hostConfig.cancelPassiveEffects;
6257
export const now = $$$hostConfig.now;
6358
export const isPrimaryRenderer = $$$hostConfig.isPrimaryRenderer;
6459
export const supportsMutation = $$$hostConfig.supportsMutation;

packages/react-test-renderer/src/ReactTestHostConfig.js

-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
* @flow
88
*/
99

10-
import * as Scheduler from 'scheduler/unstable_mock';
1110
import warning from 'shared/warning';
1211

1312
export type Type = string;
@@ -195,18 +194,10 @@ export function createTextInstance(
195194
}
196195

197196
export const isPrimaryRenderer = false;
198-
// This approach enables `now` to be mocked by tests,
199-
// Even after the reconciler has initialized and read host config values.
200-
export const now = Scheduler.unstable_now;
201-
export const scheduleDeferredCallback = Scheduler.unstable_scheduleCallback;
202-
export const cancelDeferredCallback = Scheduler.unstable_cancelCallback;
203-
export const shouldYield = Scheduler.unstable_shouldYield;
204197

205198
export const scheduleTimeout = setTimeout;
206199
export const cancelTimeout = clearTimeout;
207200
export const noTimeout = -1;
208-
export const schedulePassiveEffects = Scheduler.unstable_scheduleCallback;
209-
export const cancelPassiveEffects = Scheduler.unstable_cancelCallback;
210201

211202
// -------------------
212203
// Mutation

packages/react/src/__tests__/ReactProfiler-test.internal.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ describe('Profiler', () => {
261261
it('does not record times for components outside of Profiler tree', () => {
262262
// Mock the Scheduler module so we can track how many times the current
263263
// time is read
264-
jest.mock('scheduler/unstable_mock', obj => {
264+
jest.mock('scheduler', obj => {
265265
const ActualScheduler = require.requireActual(
266266
'scheduler/unstable_mock',
267267
);
@@ -300,8 +300,10 @@ describe('Profiler', () => {
300300
'read current time',
301301
]);
302302

303-
// Remove mock
304-
jest.unmock('scheduler/unstable_mock');
303+
// Restore original mock
304+
jest.mock('scheduler', () =>
305+
require.requireActual('scheduler/unstable_mock'),
306+
);
305307
});
306308

307309
it('logs render times for both mount and update', () => {

0 commit comments

Comments
 (0)