Experiment: Infer the current event priority from the native event (#20748)
* Add the feature flag * Add a host config method * Wire it up to the work loop * Export constants for third-party renderers * Document for third-party renderers
Dan Abramov committed
Feb 9, 2021 at 18:32 UTC
97fce318a6618bc883a8578f58b9083484c9e6d7
24 files changed
+179
-11
packages/react-art/src/ReactARTHostConfig.js
+12
@@ -7,10 +7,18 @@
7
8
import Transform from 'art/core/transform';
9
import Mode from 'art/modes/current';
10
+import {enableNewReconciler} from 'shared/ReactFeatureFlags';
11
import invariant from 'shared/invariant';
12
13
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
14
15
+import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
16
+import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
17
+
18
+const DefaultLanePriority = enableNewReconciler
19
+ ? DefaultLanePriority_new
20
+ : DefaultLanePriority_old;
21
+
22
const pooledTransform = new Transform();
23
24
const NO_CONTEXT = {};
@@ -340,6 +348,10 @@ export function shouldSetTextContent(type, props) {
348
);
349
}
350
351
+export function getCurrentEventPriority() {
352
+ return DefaultLanePriority;
353
+}
354
+
355
// The ART renderer is secondary to the React DOM renderer.
356
export const isPrimaryRenderer = false;
357
packages/react-dom/src/client/ReactDOMHostConfig.js
+17
@@ -47,6 +47,7 @@ import {validateDOMNesting, updatedAncestorInfo} from './validateDOMNesting';
47
import {
48
isEnabled as ReactBrowserEventEmitterIsEnabled,
49
setEnabled as ReactBrowserEventEmitterSetEnabled,
50
+ getEventPriority,
51
} from '../events/ReactDOMEventListener';
52
import {getChildNamespace} from '../shared/DOMNamespaces';
53
import {
@@ -65,10 +66,18 @@ import {
66
enableSuspenseServerRenderer,
67
enableCreateEventHandleAPI,
68
enableScopeAPI,
69
+ enableNewReconciler,
70
} from 'shared/ReactFeatureFlags';
71
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
72
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
73
74
+import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
75
+import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
76
+
77
+const DefaultLanePriority = enableNewReconciler
78
+ ? DefaultLanePriority_new
79
+ : DefaultLanePriority_old;
80
+
81
export type Type = string;
82
export type Props = {
83
autoFocus?: boolean,
@@ -372,6 +381,14 @@ export function createTextInstance(
381
return textNode;
382
}
383
384
+export function getCurrentEventPriority(): * {
385
+ const currentEvent = window.event;
386
+ if (currentEvent === undefined) {
387
+ return DefaultLanePriority;
388
+ }
389
+ return getEventPriority(currentEvent.type);
390
+}
391
+
392
export const isPrimaryRenderer = true;
393
export const warnsIfNotActing = true;
394
// This initialization code may run even on server environments
packages/react-dom/src/events/ReactDOMEventListener.js
+1
-1
@@ -348,7 +348,7 @@ export function attemptToDispatchEvent(
348
return null;
349
}
350
351
-function getEventPriority(domEventName: DOMEventName) {
351
+export function getEventPriority(domEventName: DOMEventName): * {
352
switch (domEventName) {
353
// Used by SimpleEventPlugin:
354
case 'cancel':
packages/react-native-renderer/src/ReactFabricHostConfig.js
+12
@@ -21,10 +21,18 @@ import type {
21
import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils';
22
import {create, diff} from './ReactNativeAttributePayload';
23
24
+import {enableNewReconciler} from 'shared/ReactFeatureFlags';
25
import invariant from 'shared/invariant';
26
27
import {dispatchEvent} from './ReactFabricEventEmitter';
28
29
+import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
30
+import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
31
+
32
+const DefaultLanePriority = enableNewReconciler
33
+ ? DefaultLanePriority_new
34
+ : DefaultLanePriority_old;
35
+
36
// Modules provided by RN:
37
import {
38
ReactNativeViewConfigRegistry,
@@ -339,6 +347,10 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
347
return false;
348
}
349
350
+export function getCurrentEventPriority(): * {
351
+ return DefaultLanePriority;
352
+}
353
+
354
// The Fabric renderer is secondary to the existing React Native renderer.
355
export const isPrimaryRenderer = false;
356
packages/react-native-renderer/src/ReactNativeHostConfig.js
+12
@@ -10,6 +10,7 @@
10
import type {TouchedViewDataAtPoint} from './ReactNativeTypes';
11
12
import invariant from 'shared/invariant';
13
+import {enableNewReconciler} from 'shared/ReactFeatureFlags';
14
15
// Modules provided by RN:
16
import {
@@ -26,6 +27,13 @@ import {
27
} from './ReactNativeComponentTree';
28
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
29
30
+import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
31
+import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
32
+
33
+const DefaultLanePriority = enableNewReconciler
34
+ ? DefaultLanePriority_new
35
+ : DefaultLanePriority_old;
36
+
37
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
38
39
export type Type = string;
@@ -261,6 +269,10 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
269
return false;
270
}
271
272
+export function getCurrentEventPriority(): * {
273
+ return DefaultLanePriority;
274
+}
275
+
276
// -------------------
277
// Mutation
278
// -------------------
packages/react-noop-renderer/src/createReactNoop.js
+4
@@ -391,6 +391,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
391
392
resetAfterCommit(): void {},
393
394
+ getCurrentEventPriority() {
395
+ return NoopRenderer.DefaultEventPriority;
396
+ },
397
+
398
now: Scheduler.unstable_now,
399
400
isPrimaryRenderer: true,
packages/react-reconciler/README.md
+26
@@ -211,6 +211,32 @@ You can proxy this to `queueMicrotask` or its equivalent in your environment.
211
212
This is a property (not a function) that should be set to `true` if your renderer is the main one on the page. For example, if you're writing a renderer for the Terminal, it makes sense to set it to `true`, but if your renderer is used *on top of* React DOM or some other existing renderer, set it to `false`.
213
214
+#### `getCurrentEventPriority`
215
+
216
+To implement this method, you'll need some constants available on the _returned_ `Renderer` object:
217
+
218
+```js
219
+const HostConfig = {
220
+ // ...
221
+ getCurrentEventPriority() {
222
+ return MyRenderer.DefaultEventPriority;
223
+ },
224
+ // ...
225
+}
226
+
227
+const MyRenderer = Reconciler(HostConfig);
228
+```
229
+
230
+The constant you return depends on which event, if any, is being handled right now. (In the browser, you can check this using `window.event && window.event.type`).
231
+
232
+* **Discrete events:** If the active event is _directly caused by the user_ (such as mouse and keyboard events) and _each event in a sequence is intentional_ (e.g. `click`), return `MyRenderer.DiscreteEventPriority`. This tells React that they should interrupt any background work and cannot be batched across time.
233
+
234
+* **Continuous events:** If the active event is _directly caused by the user_ but _the user can't distinguish between individual events in a sequence_ (e.g. `mouseover`), return `MyRenderer.ContinuousEventPriority`. This tells React they should interrupt any background work but can be batched across time.
235
+
236
+* **Other events / No active event:** In all other cases, return `MyRenderer.DefaultEventPriority`. This tells React that this event is considered background work, and interactive events will be prioritized over it.
237
+
238
+You can consult the `getCurrentEventPriority()` implementation in `ReactDOMHostConfig.js` for a reference implementation.
239
+
240
### Mutation Methods
241
242
If you're using React in mutation mode (you probably do), you'll need to implement a few more methods.
packages/react-reconciler/src/ReactFiberReconciler.js
+15
@@ -52,6 +52,9 @@ import {
52
registerMutableSourceForHydration as registerMutableSourceForHydration_old,
53
runWithPriority as runWithPriority_old,
54
getCurrentUpdateLanePriority as getCurrentUpdateLanePriority_old,
55
+ DefaultEventPriority as DefaultEventPriority_old,
56
+ DiscreteEventPriority as DiscreteEventPriority_old,
57
+ ContinuousEventPriority as ContinuousEventPriority_old,
58
} from './ReactFiberReconciler.old';
59
60
import {
@@ -92,6 +95,9 @@ import {
95
registerMutableSourceForHydration as registerMutableSourceForHydration_new,
96
runWithPriority as runWithPriority_new,
97
getCurrentUpdateLanePriority as getCurrentUpdateLanePriority_new,
98
+ DefaultEventPriority as DefaultEventPriority_new,
99
+ DiscreteEventPriority as DiscreteEventPriority_new,
100
+ ContinuousEventPriority as ContinuousEventPriority_new,
101
} from './ReactFiberReconciler.new';
102
103
export const createContainer = enableNewReconciler
@@ -168,6 +174,15 @@ export const createPortal = enableNewReconciler
174
export const createComponentSelector = enableNewReconciler
175
? createComponentSelector_new
176
: createComponentSelector_old;
177
+export const DefaultEventPriority = enableNewReconciler
178
+ ? DefaultEventPriority_new
179
+ : DefaultEventPriority_old;
180
+export const DiscreteEventPriority = enableNewReconciler
181
+ ? DiscreteEventPriority_new
182
+ : DiscreteEventPriority_old;
183
+export const ContinuousEventPriority = enableNewReconciler
184
+ ? ContinuousEventPriority_new
185
+ : ContinuousEventPriority_old;
186
187
//TODO: "psuedo" is spelled "pseudo"
188
export const createHasPsuedoClassSelector = enableNewReconciler
packages/react-reconciler/src/ReactFiberReconciler.new.js
+9
@@ -94,6 +94,15 @@ import {
94
} from './ReactFiberHotReloading.new';
95
import {markRenderScheduled} from './SchedulingProfiler';
96
97
+// Ideally host configs would import these constants from the reconciler
98
+// entry point, but we can't do this because of a circular dependency.
99
+// They are used by third-party renderers so they need to stay up to date.
100
+export {
101
+ InputDiscreteLanePriority as DiscreteEventPriority,
102
+ InputContinuousLanePriority as ContinuousEventPriority,
103
+ DefaultLanePriority as DefaultEventPriority,
104
+} from './ReactFiberLane.new';
105
+
106
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
107
export {createPortal} from './ReactPortal';
108
export {
packages/react-reconciler/src/ReactFiberReconciler.old.js
+9
@@ -94,6 +94,15 @@ import {
94
} from './ReactFiberHotReloading.old';
95
import {markRenderScheduled} from './SchedulingProfiler';
96
97
+// Ideally host configs would import these constants from the reconciler
98
+// entry point, but we can't do this because of a circular dependency.
99
+// They are used by third-party renderers so they need to stay up to date.
100
+export {
101
+ InputDiscreteLanePriority as DiscreteEventPriority,
102
+ InputContinuousLanePriority as ContinuousEventPriority,
103
+ DefaultLanePriority as DefaultEventPriority,
104
+} from './ReactFiberLane.old';
105
+
106
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
107
export {createPortal} from './ReactPortal';
108
export {
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+19
-5
@@ -35,6 +35,7 @@ import {
35
enableDoubleInvokingEffects,
36
skipUnmountedBoundaries,
37
enableTransitionEntanglement,
38
+ enableNativeEventPriorityInference,
39
} from 'shared/ReactFeatureFlags';
40
import ReactSharedInternals from 'shared/ReactSharedInternals';
41
import invariant from 'shared/invariant';
@@ -94,6 +95,7 @@ import {
95
afterActiveInstanceBlur,
96
clearContainer,
97
scheduleMicrotask,
98
+ getCurrentEventPriority,
99
} from './ReactFiberHostConfig';
100
101
import {
@@ -461,11 +463,23 @@ export function requestUpdateLane(fiber: Fiber): Lane {
463
const currentLanePriority = getCurrentUpdateLanePriority();
464
lane = findUpdateLane(currentLanePriority, currentEventWipLanes);
465
} else {
464
- const schedulerLanePriority = schedulerPriorityToLanePriority(
465
- schedulerPriority,
466
- );
467
-
468
- lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
466
+ if (enableNativeEventPriorityInference) {
467
+ const eventLanePriority = getCurrentEventPriority();
468
+ if (eventLanePriority === DefaultLanePriority) {
469
+ // TODO: move this case into the ReactDOM host config.
470
+ const schedulerLanePriority = schedulerPriorityToLanePriority(
471
+ schedulerPriority,
472
+ );
473
+ lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
474
+ } else {
475
+ lane = findUpdateLane(eventLanePriority, currentEventWipLanes);
476
+ }
477
+ } else {
478
+ const schedulerLanePriority = schedulerPriorityToLanePriority(
479
+ schedulerPriority,
480
+ );
481
+ lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
482
+ }
483
}
484
485
return lane;
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+19
-5
@@ -35,6 +35,7 @@ import {
35
enableDoubleInvokingEffects,
36
skipUnmountedBoundaries,
37
enableTransitionEntanglement,
38
+ enableNativeEventPriorityInference,
39
} from 'shared/ReactFeatureFlags';
40
import ReactSharedInternals from 'shared/ReactSharedInternals';
41
import invariant from 'shared/invariant';
@@ -94,6 +95,7 @@ import {
95
afterActiveInstanceBlur,
96
clearContainer,
97
scheduleMicrotask,
98
+ getCurrentEventPriority,
99
} from './ReactFiberHostConfig';
100
101
import {
@@ -461,11 +463,23 @@ export function requestUpdateLane(fiber: Fiber): Lane {
463
const currentLanePriority = getCurrentUpdateLanePriority();
464
lane = findUpdateLane(currentLanePriority, currentEventWipLanes);
465
} else {
464
- const schedulerLanePriority = schedulerPriorityToLanePriority(
465
- schedulerPriority,
466
- );
467
-
468
- lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
466
+ if (enableNativeEventPriorityInference) {
467
+ const eventLanePriority = getCurrentEventPriority();
468
+ if (eventLanePriority === DefaultLanePriority) {
469
+ // TODO: move this case into the ReactDOM host config.
470
+ const schedulerLanePriority = schedulerPriorityToLanePriority(
471
+ schedulerPriority,
472
+ );
473
+ lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
474
+ } else {
475
+ lane = findUpdateLane(eventLanePriority, currentEventWipLanes);
476
+ }
477
+ } else {
478
+ const schedulerLanePriority = schedulerPriorityToLanePriority(
479
+ schedulerPriority,
480
+ );
481
+ lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
482
+ }
483
}
484
485
return lane;
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+1
@@ -73,6 +73,7 @@ export const afterActiveInstanceBlur = $$$hostConfig.afterActiveInstanceBlur;
73
export const preparePortalMount = $$$hostConfig.preparePortalMount;
74
export const prepareScopeUpdate = $$$hostConfig.preparePortalMount;
75
export const getInstanceFromScope = $$$hostConfig.getInstanceFromScope;
76
+export const getCurrentEventPriority = $$$hostConfig.getCurrentEventPriority;
77
78
// -------------------
79
// Test selectors
packages/react-test-renderer/src/ReactTestHostConfig.js
+12
@@ -8,6 +8,14 @@
8
*/
9
10
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
11
+import {enableNewReconciler} from 'shared/ReactFeatureFlags';
12
+
13
+import {DefaultLanePriority as DefaultLanePriority_old} from 'react-reconciler/src/ReactFiberLane.old';
14
+import {DefaultLanePriority as DefaultLanePriority_new} from 'react-reconciler/src/ReactFiberLane.new';
15
+
16
+const DefaultLanePriority = enableNewReconciler
17
+ ? DefaultLanePriority_new
18
+ : DefaultLanePriority_old;
19
20
export type Type = string;
21
export type Props = Object;
@@ -213,6 +221,10 @@ export function createTextInstance(
221
};
222
}
223
224
+export function getCurrentEventPriority(): * {
225
+ return DefaultLanePriority;
226
+}
227
+
228
export const isPrimaryRenderer = false;
229
export const warnsIfNotActing = true;
230
packages/shared/ReactFeatureFlags.js
+2
@@ -151,3 +151,5 @@ export const disableSchedulerTimeoutInWorkLoop = false;
151
export const enableTransitionEntanglement = false;
152
153
export const enableDiscreteEventMicroTasks = false;
154
+
155
+export const enableNativeEventPriorityInference = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -59,6 +59,7 @@ export const enableRecursiveCommitTraversal = false;
59
export const disableSchedulerTimeoutInWorkLoop = false;
60
export const enableTransitionEntanglement = false;
61
export const enableDiscreteEventMicroTasks = false;
62
+export const enableNativeEventPriorityInference = false;
63
64
// Flow magic to verify the exports of this file match the original version.
65
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -58,6 +58,7 @@ export const enableRecursiveCommitTraversal = false;
58
export const disableSchedulerTimeoutInWorkLoop = false;
59
export const enableTransitionEntanglement = false;
60
export const enableDiscreteEventMicroTasks = false;
61
+export const enableNativeEventPriorityInference = false;
62
63
// Flow magic to verify the exports of this file match the original version.
64
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -58,6 +58,7 @@ export const enableRecursiveCommitTraversal = false;
58
export const disableSchedulerTimeoutInWorkLoop = false;
59
export const enableTransitionEntanglement = false;
60
export const enableDiscreteEventMicroTasks = false;
61
+export const enableNativeEventPriorityInference = false;
62
63
// Flow magic to verify the exports of this file match the original version.
64
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -58,6 +58,7 @@ export const enableRecursiveCommitTraversal = false;
58
export const disableSchedulerTimeoutInWorkLoop = false;
59
export const enableTransitionEntanglement = false;
60
export const enableDiscreteEventMicroTasks = false;
61
+export const enableNativeEventPriorityInference = false;
62
63
// Flow magic to verify the exports of this file match the original version.
64
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -58,6 +58,7 @@ export const enableRecursiveCommitTraversal = false;
58
export const disableSchedulerTimeoutInWorkLoop = false;
59
export const enableTransitionEntanglement = false;
60
export const enableDiscreteEventMicroTasks = false;
61
+export const enableNativeEventPriorityInference = false;
62
63
// Flow magic to verify the exports of this file match the original version.
64
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -58,6 +58,7 @@ export const enableRecursiveCommitTraversal = false;
58
export const disableSchedulerTimeoutInWorkLoop = false;
59
export const enableTransitionEntanglement = false;
60
export const enableDiscreteEventMicroTasks = false;
61
+export const enableNativeEventPriorityInference = false;
62
63
// Flow magic to verify the exports of this file match the original version.
64
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1
@@ -58,6 +58,7 @@ export const enableRecursiveCommitTraversal = false;
58
export const disableSchedulerTimeoutInWorkLoop = false;
59
export const enableTransitionEntanglement = false;
60
export const enableDiscreteEventMicroTasks = false;
61
+export const enableNativeEventPriorityInference = false;
62
63
// Flow magic to verify the exports of this file match the original version.
64
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -57,3 +57,4 @@ export const enableProfilerNestedUpdateScheduledHook = __VARIANT__;
57
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
58
export const enableTransitionEntanglement = __VARIANT__;
59
export const enableDiscreteEventMicroTasks = __VARIANT__;
60
+export const enableNativeEventPriorityInference = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -33,6 +33,7 @@ export const {
33
disableSchedulerTimeoutInWorkLoop,
34
enableTransitionEntanglement,
35
enableDiscreteEventMicroTasks,
36
+ enableNativeEventPriorityInference,
37
} = dynamicFeatureFlags;
38
39
// On WWW, __EXPERIMENTAL__ is used for a new modern build.