Skip to content

Commit d3ec420

Browse files
authored
Address feedback for accumulateTwoPhaseListeners (facebook#18289)
1 parent cd48a06 commit d3ec420

13 files changed

+315
-147
lines changed

packages/legacy-events/EventPropagators.js

+21-75
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,19 @@
33
*
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
68
*/
79

8-
import {
9-
getParentInstance,
10-
traverseTwoPhase,
11-
traverseEnterLeave,
12-
} from 'shared/ReactTreeTraversal';
10+
import type {Fiber} from 'react-reconciler/src/ReactFiber';
11+
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
1312

1413
import getListener from 'legacy-events/getListener';
14+
15+
import {traverseEnterLeave} from 'shared/ReactTreeTraversal';
1516
import accumulateInto from './accumulateInto';
1617
import forEachAccumulated from './forEachAccumulated';
1718

18-
type PropagationPhases = 'bubbled' | 'captured';
19-
20-
/**
21-
* Some event types have a notion of different registration names for different
22-
* "phases" of propagation. This finds listeners by a given phase.
23-
*/
24-
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
25-
const registrationName =
26-
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
27-
return getListener(inst, registrationName);
28-
}
29-
3019
/**
3120
* A small set of propagation patterns, each of which will accept a small amount
3221
* of information, and generate a set of "dispatch ready event objects" - which
@@ -37,58 +26,16 @@ function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
3726
* single one.
3827
*/
3928

40-
/**
41-
* Tags a `SyntheticEvent` with dispatched listeners. Creating this function
42-
* here, allows us to not have to bind or create functions for each event.
43-
* Mutating the event's members allows us to not have to create a wrapping
44-
* "dispatch" object that pairs the event with the listener.
45-
*/
46-
function accumulateDirectionalDispatches(inst, phase, event) {
47-
if (__DEV__) {
48-
if (!inst) {
49-
console.error('Dispatching inst must not be null');
50-
}
51-
}
52-
const listener = listenerAtPhase(inst, event, phase);
53-
if (listener) {
54-
event._dispatchListeners = accumulateInto(
55-
event._dispatchListeners,
56-
listener,
57-
);
58-
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
59-
}
60-
}
61-
62-
/**
63-
* Collect dispatches (must be entirely collected before dispatching - see unit
64-
* tests). Lazily allocate the array to conserve memory. We must loop through
65-
* each event and perform the traversal for each one. We cannot perform a
66-
* single traversal for the entire collection of events because each event may
67-
* have a different target.
68-
*/
69-
export function accumulateTwoPhaseDispatchesSingle(event) {
70-
if (event && event.dispatchConfig.phasedRegistrationNames) {
71-
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
72-
}
73-
}
74-
75-
/**
76-
* Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
77-
*/
78-
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
79-
if (event && event.dispatchConfig.phasedRegistrationNames) {
80-
const targetInst = event._targetInst;
81-
const parentInst = targetInst ? getParentInstance(targetInst) : null;
82-
traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
83-
}
84-
}
85-
8629
/**
8730
* Accumulates without regard to direction, does not look for phased
8831
* registration names. Same as `accumulateDirectDispatchesSingle` but without
8932
* requiring that the `dispatchMarker` be the same as the dispatched ID.
9033
*/
91-
function accumulateDispatches(inst, ignoredDirection, event) {
34+
function accumulateDispatches(
35+
inst: Fiber,
36+
ignoredDirection: ?boolean,
37+
event: ReactSyntheticEvent,
38+
): void {
9239
if (inst && event && event.dispatchConfig.registrationName) {
9340
const registrationName = event.dispatchConfig.registrationName;
9441
const listener = getListener(inst, registrationName);
@@ -107,24 +54,23 @@ function accumulateDispatches(inst, ignoredDirection, event) {
10754
* `dispatchMarker`.
10855
* @param {SyntheticEvent} event
10956
*/
110-
function accumulateDirectDispatchesSingle(event) {
57+
function accumulateDirectDispatchesSingle(event: ReactSyntheticEvent) {
11158
if (event && event.dispatchConfig.registrationName) {
11259
accumulateDispatches(event._targetInst, null, event);
11360
}
11461
}
11562

116-
export function accumulateTwoPhaseDispatches(events) {
117-
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
118-
}
119-
120-
export function accumulateTwoPhaseDispatchesSkipTarget(events) {
121-
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
122-
}
123-
124-
export function accumulateEnterLeaveDispatches(leave, enter, from, to) {
63+
export function accumulateEnterLeaveDispatches(
64+
leave: ReactSyntheticEvent,
65+
enter: ReactSyntheticEvent,
66+
from: Fiber,
67+
to: Fiber,
68+
) {
12569
traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
12670
}
12771

128-
export function accumulateDirectDispatches(events) {
72+
export function accumulateDirectDispatches(
73+
events: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent),
74+
) {
12975
forEachAccumulated(events, accumulateDirectDispatchesSingle);
13076
}

packages/legacy-events/ReactSyntheticEventType.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export type ReactSyntheticEvent = {|
3131
nativeEventTarget: EventTarget,
3232
) => ReactSyntheticEvent,
3333
isPersistent: () => boolean,
34-
_dispatchInstances: null | Array<Fiber>,
35-
_dispatchListeners: null | Array<Function>,
36-
_targetInst: null | Fiber,
34+
_dispatchInstances: null | Array<Fiber> | Fiber,
35+
_dispatchListeners: null | Array<Function> | Function,
36+
_targetInst: Fiber,
3737
type: string,
3838
|};

packages/legacy-events/ResponderEventPlugin.js

+58-6
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {getLowestCommonAncestor, isAncestor} from 'shared/ReactTreeTraversal';
8+
import {
9+
getLowestCommonAncestor,
10+
isAncestor,
11+
getParentInstance,
12+
traverseTwoPhase,
13+
} from 'shared/ReactTreeTraversal';
914

1015
import {
1116
executeDirectDispatch,
1217
hasDispatches,
1318
executeDispatchesInOrderStopAtTrue,
1419
getInstanceFromNode,
1520
} from './EventPluginUtils';
16-
import {
17-
accumulateDirectDispatches,
18-
accumulateTwoPhaseDispatches,
19-
accumulateTwoPhaseDispatchesSkipTarget,
20-
} from './EventPropagators';
21+
import {accumulateDirectDispatches} from './EventPropagators';
2122
import ResponderSyntheticEvent from './ResponderSyntheticEvent';
2223
import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
2324
import accumulate from './accumulate';
@@ -32,6 +33,9 @@ import {
3233
moveDependencies,
3334
endDependencies,
3435
} from './ResponderTopLevelEventTypes';
36+
import getListener from './getListener';
37+
import accumulateInto from './accumulateInto';
38+
import forEachAccumulated from './forEachAccumulated';
3539

3640
/**
3741
* Instance of element that should respond to touch/move types of interactions,
@@ -151,6 +155,54 @@ const eventTypes = {
151155
},
152156
};
153157

158+
// Start of inline: the below functions were inlined from
159+
// EventPropagator.js, as they deviated from ReactDOM's newer
160+
// implementations.
161+
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
162+
const registrationName =
163+
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
164+
return getListener(inst, registrationName);
165+
}
166+
167+
function accumulateDirectionalDispatches(inst, phase, event) {
168+
if (__DEV__) {
169+
if (!inst) {
170+
console.error('Dispatching inst must not be null');
171+
}
172+
}
173+
const listener = listenerAtPhase(inst, event, phase);
174+
if (listener) {
175+
event._dispatchListeners = accumulateInto(
176+
event._dispatchListeners,
177+
listener,
178+
);
179+
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
180+
}
181+
}
182+
183+
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
184+
if (event && event.dispatchConfig.phasedRegistrationNames) {
185+
const targetInst = event._targetInst;
186+
const parentInst = targetInst ? getParentInstance(targetInst) : null;
187+
traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
188+
}
189+
}
190+
191+
function accumulateTwoPhaseDispatchesSkipTarget(events) {
192+
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
193+
}
194+
195+
function accumulateTwoPhaseDispatchesSingle(event) {
196+
if (event && event.dispatchConfig.phasedRegistrationNames) {
197+
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
198+
}
199+
}
200+
201+
function accumulateTwoPhaseDispatches(events) {
202+
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
203+
}
204+
// End of inline
205+
154206
/**
155207
*
156208
* Responder System:

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

-6
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ import {
5050
eventNameDispatchConfigs,
5151
injectEventPluginsByName,
5252
} from 'legacy-events/EventPluginRegistry';
53-
import {
54-
accumulateTwoPhaseDispatches,
55-
accumulateDirectDispatches,
56-
} from 'legacy-events/EventPropagators';
5753
import ReactVersion from 'shared/ReactVersion';
5854
import invariant from 'shared/invariant';
5955
import {warnUnstableRenderSubtreeIntoContainer} from 'shared/ReactFeatureFlags';
@@ -184,8 +180,6 @@ const Internals = {
184180
getFiberCurrentPropsFromNode,
185181
injectEventPluginsByName,
186182
eventNameDispatchConfigs,
187-
accumulateTwoPhaseDispatches,
188-
accumulateDirectDispatches,
189183
enqueueStateRestore,
190184
restoreStateIfNeeded,
191185
dispatchEvent,

packages/react-dom/src/events/BeforeInputEventPlugin.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from './FallbackCompositionState';
2929
import SyntheticCompositionEvent from './SyntheticCompositionEvent';
3030
import SyntheticInputEvent from './SyntheticInputEvent';
31-
import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
31+
import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
3232

3333
const END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
3434
const START_KEYCODE = 229;

packages/react-dom/src/events/ChangeEventPlugin.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import isEventSupported from './isEventSupported';
2727
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
2828
import {updateValueIfChanged} from '../client/inputValueTracking';
2929
import {setDefaultValue} from '../client/ReactDOMInput';
30-
import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
3130

3231
import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags';
32+
import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
3333

3434
const eventTypes = {
3535
change: {

packages/react-dom/src/events/DOMModernPluginEventSystem.js

+1-45
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
1919
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
2020
import {executeDispatchesInOrder} from 'legacy-events/EventPluginUtils';
2121
import {plugins} from 'legacy-events/EventPluginRegistry';
22-
import getListener from 'legacy-events/getListener';
2322

24-
import {HostRoot, HostPortal, HostComponent} from 'shared/ReactWorkTags';
23+
import {HostRoot, HostPortal} from 'shared/ReactWorkTags';
2524

2625
import {addTrappedEventListener} from './ReactDOMEventListener';
2726
import getEventTarget from './getEventTarget';
@@ -315,46 +314,3 @@ export function attachElementListener(listener: ReactDOMListener): void {
315314
export function detachElementListener(listener: ReactDOMListener): void {
316315
// TODO
317316
}
318-
319-
export function accumulateTwoPhaseListeners(event: ReactSyntheticEvent): void {
320-
const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
321-
if (phasedRegistrationNames == null) {
322-
return;
323-
}
324-
const {bubbled, captured} = phasedRegistrationNames;
325-
const dispatchListeners = [];
326-
const dispatchInstances = [];
327-
let node = event._targetInst;
328-
let hasListeners = false;
329-
330-
// Accumulate all instances and listeners via the target -> root path.
331-
while (node !== null) {
332-
// We only care for listeners that are on HostComponents (i.e. <div>)
333-
if (node.tag === HostComponent) {
334-
// Standard React on* listeners, i.e. onClick prop
335-
const captureListener = getListener(node, captured);
336-
if (captureListener != null) {
337-
hasListeners = true;
338-
// Capture listeners/instances should go at the start, so we
339-
// unshift them to the start of the array.
340-
dispatchListeners.unshift(captureListener);
341-
dispatchInstances.unshift(node);
342-
}
343-
const bubbleListener = getListener(node, bubbled);
344-
if (bubbleListener != null) {
345-
hasListeners = true;
346-
// Bubble listeners/instances should go at the end, so we
347-
// push them to the end of the array.
348-
dispatchListeners.push(bubbleListener);
349-
dispatchInstances.push(node);
350-
}
351-
}
352-
node = node.return;
353-
}
354-
// To prevent allocation to the event unless we actually
355-
// have listeners we use the flag we would have set above.
356-
if (hasListeners) {
357-
event._dispatchListeners = dispatchListeners;
358-
event._dispatchInstances = dispatchInstances;
359-
}
360-
}

packages/react-dom/src/events/SelectEventPlugin.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
2626
import {hasSelectionCapabilities} from '../client/ReactInputSelection';
2727
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
2828
import {isListeningToAllDependencies} from './DOMEventListenerMap';
29-
import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
29+
import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
3030

3131
const skipSelectionChangeEvent =
3232
canUseDOM && 'documentMode' in document && document.documentMode <= 11;

packages/react-dom/src/events/SimpleEventPlugin.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import SyntheticTransitionEvent from './SyntheticTransitionEvent';
3636
import SyntheticUIEvent from './SyntheticUIEvent';
3737
import SyntheticWheelEvent from './SyntheticWheelEvent';
3838
import getEventCharCode from './getEventCharCode';
39-
import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
39+
import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
4040

4141
// Only used in DEV for exhaustiveness validation.
4242
const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [

0 commit comments

Comments
 (0)