mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ReactDOM.useEvent: support custom types (#18351)
* ReactDOM.useEvent: support custom types
This commit is contained in:
@@ -13,19 +13,27 @@ import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type DispatchConfig = {|
|
||||
dependencies: Array<TopLevelType>,
|
||||
phasedRegistrationNames?: {|
|
||||
bubbled: string,
|
||||
captured: string,
|
||||
dependencies?: Array<TopLevelType>,
|
||||
phasedRegistrationNames: {|
|
||||
bubbled: null | string,
|
||||
captured: null | string,
|
||||
|},
|
||||
registrationName?: string,
|
||||
eventPriority: EventPriority,
|
||||
|};
|
||||
|
||||
export type CustomDispatchConfig = {|
|
||||
phasedRegistrationNames: {|
|
||||
bubbled: null,
|
||||
captured: null,
|
||||
|},
|
||||
customEvent: true,
|
||||
|};
|
||||
|
||||
export type ReactSyntheticEvent = {|
|
||||
dispatchConfig: DispatchConfig,
|
||||
dispatchConfig: DispatchConfig | CustomDispatchConfig,
|
||||
getPooled: (
|
||||
dispatchConfig: DispatchConfig,
|
||||
dispatchConfig: DispatchConfig | CustomDispatchConfig,
|
||||
targetInst: Fiber,
|
||||
nativeTarget: Event,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
+5
-2
@@ -12,7 +12,10 @@ import type {
|
||||
TopLevelType,
|
||||
DOMTopLevelEventType,
|
||||
} from 'legacy-events/TopLevelEventTypes';
|
||||
import type {DispatchConfig} from 'legacy-events/ReactSyntheticEventType';
|
||||
import type {
|
||||
DispatchConfig,
|
||||
CustomDispatchConfig,
|
||||
} from 'legacy-events/ReactSyntheticEventType';
|
||||
|
||||
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
|
||||
import {
|
||||
@@ -31,7 +34,7 @@ export const simpleEventPluginEventTypes = {};
|
||||
|
||||
export const topLevelEventsToDispatchConfig: Map<
|
||||
TopLevelType,
|
||||
DispatchConfig,
|
||||
DispatchConfig | CustomDispatchConfig,
|
||||
> = new Map();
|
||||
|
||||
const eventPriorities = new Map();
|
||||
|
||||
+27
-2
@@ -17,7 +17,10 @@ import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {PluginModule} from 'legacy-events/PluginModuleType';
|
||||
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
|
||||
import type {
|
||||
ReactSyntheticEvent,
|
||||
CustomDispatchConfig,
|
||||
} from 'legacy-events/ReactSyntheticEventType';
|
||||
import type {ReactDOMListener} from 'shared/ReactDOMTypes';
|
||||
|
||||
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
|
||||
@@ -79,6 +82,7 @@ import {
|
||||
COMMENT_NODE,
|
||||
ELEMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
import {topLevelEventsToDispatchConfig} from './DOMEventProperties';
|
||||
|
||||
import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
|
||||
|
||||
@@ -118,6 +122,14 @@ const capturePhaseEvents = new Set([
|
||||
TOP_WAITING,
|
||||
]);
|
||||
|
||||
const emptyDispatchConfigForCustomEvents: CustomDispatchConfig = {
|
||||
customEvent: true,
|
||||
phasedRegistrationNames: {
|
||||
bubbled: null,
|
||||
captured: null,
|
||||
},
|
||||
};
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
function dispatchEventsForPlugins(
|
||||
@@ -419,8 +431,21 @@ export function attachElementListener(listener: ReactDOMListener): void {
|
||||
listeners = new Set();
|
||||
initListenersSet(target, listeners);
|
||||
}
|
||||
// Finally, add our listener to the listeners Set.
|
||||
// Add our listener to the listeners Set.
|
||||
listeners.add(listener);
|
||||
// Finally, add the event to our known event types list.
|
||||
let dispatchConfig = topLevelEventsToDispatchConfig.get(type);
|
||||
// If we don't have a dispatchConfig, then we're dealing with
|
||||
// an event type that React does not know about (i.e. a custom event).
|
||||
// We need to register an event config for this or the SimpleEventPlugin
|
||||
// will not appropriately provide a SyntheticEvent, so we use out empty
|
||||
// dispatch config for custom events.
|
||||
if (dispatchConfig === undefined) {
|
||||
topLevelEventsToDispatchConfig.set(
|
||||
type,
|
||||
emptyDispatchConfigForCustomEvents,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function detachElementListener(listener: ReactDOMListener): void {
|
||||
|
||||
+4
-1
@@ -172,7 +172,10 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
break;
|
||||
default:
|
||||
if (__DEV__) {
|
||||
if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) {
|
||||
if (
|
||||
knownHTMLTopLevelTypes.indexOf(topLevelType) === -1 &&
|
||||
dispatchConfig.customEvent !== true
|
||||
) {
|
||||
console.error(
|
||||
'SimpleEventPlugin: Unhandled event type, `%s`. This warning ' +
|
||||
'is likely caused by a bug in React. Please file an issue.',
|
||||
|
||||
+80
-2
@@ -15,12 +15,16 @@ let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let Scheduler;
|
||||
|
||||
function dispatchClickEvent(element) {
|
||||
function dispatchEvent(element, type) {
|
||||
const event = document.createEvent('Event');
|
||||
event.initEvent('click', true, true);
|
||||
event.initEvent(type, true, true);
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function dispatchClickEvent(element) {
|
||||
dispatchEvent(element, 'click');
|
||||
}
|
||||
|
||||
describe('DOMModernPluginEventSystem', () => {
|
||||
let container;
|
||||
|
||||
@@ -1782,6 +1786,80 @@ describe('DOMModernPluginEventSystem', () => {
|
||||
dispatchClickEvent(button);
|
||||
expect(clickEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('handles propagation of custom user events', () => {
|
||||
const buttonRef = React.createRef();
|
||||
const divRef = React.createRef();
|
||||
const log = [];
|
||||
const onCustomEvent = jest.fn(e =>
|
||||
log.push(['bubble', e.currentTarget]),
|
||||
);
|
||||
const onCustomEventCapture = jest.fn(e =>
|
||||
log.push(['capture', e.currentTarget]),
|
||||
);
|
||||
|
||||
function Test() {
|
||||
let customEventHandle;
|
||||
|
||||
// Test that we get a warning when we don't provide an explicit priortiy
|
||||
expect(() => {
|
||||
customEventHandle = ReactDOM.unstable_useEvent('custom-event');
|
||||
}).toWarnDev(
|
||||
'Warning: The event "type" provided to useEvent() does not have a known priority type. ' +
|
||||
'It is recommended to provide a "priority" option to specify a priority.',
|
||||
);
|
||||
|
||||
customEventHandle = ReactDOM.unstable_useEvent('custom-event', {
|
||||
priority: 0, // Discrete
|
||||
});
|
||||
|
||||
const customCaptureHandle = ReactDOM.unstable_useEvent(
|
||||
'custom-event',
|
||||
{
|
||||
capture: true,
|
||||
priority: 0, // Discrete
|
||||
},
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
customEventHandle.setListener(buttonRef.current, onCustomEvent);
|
||||
customCaptureHandle.setListener(
|
||||
buttonRef.current,
|
||||
onCustomEventCapture,
|
||||
);
|
||||
customEventHandle.setListener(divRef.current, onCustomEvent);
|
||||
customCaptureHandle.setListener(
|
||||
divRef.current,
|
||||
onCustomEventCapture,
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<button ref={buttonRef}>
|
||||
<div ref={divRef}>Click me!</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchEvent(buttonElement, 'custom-event');
|
||||
expect(onCustomEvent).toHaveBeenCalledTimes(1);
|
||||
expect(onCustomEventCapture).toHaveBeenCalledTimes(1);
|
||||
expect(log[0]).toEqual(['capture', buttonElement]);
|
||||
expect(log[1]).toEqual(['bubble', buttonElement]);
|
||||
|
||||
let divElement = divRef.current;
|
||||
dispatchEvent(divElement, 'custom-event');
|
||||
expect(onCustomEvent).toHaveBeenCalledTimes(3);
|
||||
expect(onCustomEventCapture).toHaveBeenCalledTimes(3);
|
||||
expect(log[2]).toEqual(['capture', buttonElement]);
|
||||
expect(log[3]).toEqual(['capture', divElement]);
|
||||
expect(log[4]).toEqual(['bubble', divElement]);
|
||||
expect(log[5]).toEqual(['bubble', buttonElement]);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
+17
-16
@@ -20,12 +20,9 @@ export default function accumulateTwoPhaseListeners(
|
||||
accumulateUseEventListeners?: boolean,
|
||||
): void {
|
||||
const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
|
||||
if (phasedRegistrationNames == null) {
|
||||
return;
|
||||
}
|
||||
const {bubbled, captured} = phasedRegistrationNames;
|
||||
const dispatchListeners = [];
|
||||
const dispatchInstances = [];
|
||||
const {bubbled, captured} = phasedRegistrationNames;
|
||||
let node = event._targetInst;
|
||||
|
||||
// Accumulate all instances and listeners via the target -> root path.
|
||||
@@ -60,19 +57,23 @@ export default function accumulateTwoPhaseListeners(
|
||||
}
|
||||
}
|
||||
// Standard React on* listeners, i.e. onClick prop
|
||||
const captureListener = getListener(node, captured);
|
||||
if (captureListener != null) {
|
||||
// Capture listeners/instances should go at the start, so we
|
||||
// unshift them to the start of the array.
|
||||
dispatchListeners.unshift(captureListener);
|
||||
dispatchInstances.unshift(node);
|
||||
if (captured !== null) {
|
||||
const captureListener = getListener(node, captured);
|
||||
if (captureListener != null) {
|
||||
// Capture listeners/instances should go at the start, so we
|
||||
// unshift them to the start of the array.
|
||||
dispatchListeners.unshift(captureListener);
|
||||
dispatchInstances.unshift(node);
|
||||
}
|
||||
}
|
||||
const bubbleListener = getListener(node, bubbled);
|
||||
if (bubbleListener != null) {
|
||||
// Bubble listeners/instances should go at the end, so we
|
||||
// push them to the end of the array.
|
||||
dispatchListeners.push(bubbleListener);
|
||||
dispatchInstances.push(node);
|
||||
if (bubbled !== null) {
|
||||
const bubbleListener = getListener(node, bubbled);
|
||||
if (bubbleListener != null) {
|
||||
// Bubble listeners/instances should go at the end, so we
|
||||
// push them to the end of the array.
|
||||
dispatchListeners.push(bubbleListener);
|
||||
dispatchInstances.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
node = node.return;
|
||||
|
||||
Reference in New Issue
Block a user