Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental Event API: Remove "listener" from event objects #15391

Merged
merged 3 commits into from
Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 101 additions & 69 deletions packages/react-dom/src/events/DOMEventResponderSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import warning from 'shared/warning';
import {enableEventAPI} from 'shared/ReactFeatureFlags';
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
import invariant from 'shared/invariant';

import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';

Expand All @@ -50,7 +51,6 @@ type EventQueue = {
};

type PartialEventObject = {
listener: ($Shape<PartialEventObject>) => void,
target: Element | Document,
type: string,
};
Expand All @@ -76,22 +76,31 @@ const targetEventTypeCached: Map<
Set<DOMTopLevelEventType>,
> = new Map();
const ownershipChangeListeners: Set<ReactEventComponentInstance> = new Set();
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
const eventListeners:
| WeakMap
| Map<
$Shape<PartialEventObject>,
($Shape<PartialEventObject>) => void,
> = new PossiblyWeakMap();

let currentTimers = new Map();
let currentOwner = null;
let currentInstance: ReactEventComponentInstance;
let currentEventQueue: EventQueue;
let currentInstance: null | ReactEventComponentInstance = null;
let currentEventQueue: null | EventQueue = null;

const eventResponderContext: ReactResponderContext = {
dispatchEvent(
possibleEventObject: Object,
listener: ($Shape<PartialEventObject>) => void,
{capture, discrete}: ReactResponderDispatchEventOptions,
): void {
const {listener, target, type} = possibleEventObject;
validateResponderContext();
const {target, type} = possibleEventObject;

if (listener == null || target == null || type == null) {
if (target == null || type == null) {
throw new Error(
'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
'context.dispatchEvent: "target" and "type" fields on event object are required.',
);
}
if (__DEV__) {
Expand All @@ -115,15 +124,18 @@ const eventResponderContext: ReactResponderContext = {
>);
const events = getEventsFromEventQueue(capture);
if (discrete) {
currentEventQueue.discrete = true;
((currentEventQueue: any): EventQueue).discrete = true;
}
eventListeners.set(eventObject, listener);
events.push(eventObject);
},
dispatchStopPropagation(capture?: boolean) {
validateResponderContext();
const events = getEventsFromEventQueue();
events.push({stopPropagation: true});
},
isPositionWithinTouchHitTarget(doc: Document, x: number, y: number): boolean {
validateResponderContext();
// This isn't available in some environments (JSDOM)
if (typeof doc.elementFromPoint !== 'function') {
return false;
Expand Down Expand Up @@ -151,6 +163,7 @@ const eventResponderContext: ReactResponderContext = {
return false;
},
isTargetWithinEventComponent(target: Element | Document): boolean {
validateResponderContext();
if (target != null) {
let fiber = getClosestInstanceFromNode(target);
while (fiber !== null) {
Expand Down Expand Up @@ -182,6 +195,7 @@ const eventResponderContext: ReactResponderContext = {
doc: Document,
rootEventTypes: Array<ReactEventResponderEventType>,
): void {
validateResponderContext();
listenToResponderEventTypesImpl(rootEventTypes, doc);
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
Expand All @@ -197,12 +211,15 @@ const eventResponderContext: ReactResponderContext = {
rootEventComponentInstances,
);
}
rootEventComponentInstances.add(currentInstance);
rootEventComponentInstances.add(
((currentInstance: any): ReactEventComponentInstance),
);
}
},
removeRootEventTypes(
rootEventTypes: Array<ReactEventResponderEventType>,
): void {
validateResponderContext();
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const topLevelEventType =
Expand All @@ -211,14 +228,18 @@ const eventResponderContext: ReactResponderContext = {
topLevelEventType,
);
if (rootEventComponents !== undefined) {
rootEventComponents.delete(currentInstance);
rootEventComponents.delete(
((currentInstance: any): ReactEventComponentInstance),
);
}
}
},
hasOwnership(): boolean {
validateResponderContext();
return currentOwner === currentInstance;
},
requestOwnership(): boolean {
validateResponderContext();
if (currentOwner !== null) {
return false;
}
Expand All @@ -227,6 +248,7 @@ const eventResponderContext: ReactResponderContext = {
return true;
},
releaseOwnership(): boolean {
validateResponderContext();
if (currentOwner !== currentInstance) {
return false;
}
Expand All @@ -235,6 +257,7 @@ const eventResponderContext: ReactResponderContext = {
return false;
},
setTimeout(func: () => void, delay): Symbol {
validateResponderContext();
if (currentTimers === null) {
currentTimers = new Map();
}
Expand All @@ -253,14 +276,15 @@ const eventResponderContext: ReactResponderContext = {
currentTimers.set(delay, timeout);
}
timeout.timers.set(timerId, {
instance: currentInstance,
instance: ((currentInstance: any): ReactEventComponentInstance),
func,
id: timerId,
});
activeTimeouts.set(timerId, timeout);
return timerId;
},
clearTimeout(timerId: Symbol): void {
validateResponderContext();
const timeout = activeTimeouts.get(timerId);

if (timeout !== undefined) {
Expand All @@ -279,6 +303,7 @@ const eventResponderContext: ReactResponderContext = {
node: Element,
props: null | Object,
}> {
validateResponderContext();
const eventTargetHostComponents = [];
let node = getClosestInstanceFromNode(target);
// We traverse up the fiber tree from the target fiber, to the
Expand Down Expand Up @@ -326,28 +351,26 @@ const eventResponderContext: ReactResponderContext = {
};

function getEventsFromEventQueue(capture?: boolean): Array<EventObjectTypes> {
const eventQueue = ((currentEventQueue: any): EventQueue);
let events;
if (capture) {
events = currentEventQueue.capture;
events = eventQueue.capture;
if (events === null) {
events = currentEventQueue.capture = [];
events = eventQueue.capture = [];
}
} else {
events = currentEventQueue.bubble;
events = eventQueue.bubble;
if (events === null) {
events = currentEventQueue.bubble = [];
events = eventQueue.bubble = [];
}
}
return events;
}

function processTimers(timers: Map<Symbol, ResponderTimer>): void {
const previousEventQueue = currentEventQueue;
const previousInstance = currentInstance;
const timersArr = Array.from(timers.values());
currentEventQueue = createEventQueue();

try {
const timersArr = Array.from(timers.values());
for (let i = 0; i < timersArr.length; i++) {
const {instance, func, id} = timersArr[i];
currentInstance = instance;
Expand All @@ -359,9 +382,9 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
}
batchedUpdates(processEventQueue, currentEventQueue);
} finally {
currentInstance = previousInstance;
currentEventQueue = previousEventQueue;
currentTimers = null;
currentInstance = null;
currentEventQueue = null;
}
}

Expand Down Expand Up @@ -404,7 +427,9 @@ function createEventQueue(): EventQueue {

function processEvent(event: $Shape<PartialEventObject>): void {
const type = event.type;
const listener = event.listener;
const listener = ((eventListeners.get(event): any): (
$Shape<PartialEventObject>,
) => void);
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
}

Expand Down Expand Up @@ -435,7 +460,7 @@ function processEvents(
}

export function processEventQueue(): void {
const {bubble, capture, discrete} = currentEventQueue;
const {bubble, capture, discrete} = ((currentEventQueue: any): EventQueue);

if (discrete) {
interactiveUpdates(() => {
Expand Down Expand Up @@ -478,13 +503,8 @@ function handleTopLevelType(
return;
}
}
const previousInstance = currentInstance;
currentInstance = eventComponentInstance;
try {
responder.onEvent(responderEvent, eventResponderContext, props, state);
} finally {
currentInstance = previousInstance;
}
responder.onEvent(responderEvent, eventResponderContext, props, state);
}

export function runResponderEventsInBatch(
Expand All @@ -502,54 +522,60 @@ export function runResponderEventsInBatch(
((nativeEventTarget: any): Element | Document),
eventSystemFlags,
);
let node = targetFiber;
// Traverse up the fiber tree till we find event component fibers.
while (node !== null) {
if (node.tag === EventComponent) {
const eventComponentInstance = node.stateNode;
handleTopLevelType(
topLevelType,
responderEvent,
eventComponentInstance,
false,
);

try {
let node = targetFiber;
// Traverse up the fiber tree till we find event component fibers.
while (node !== null) {
if (node.tag === EventComponent) {
const eventComponentInstance = node.stateNode;
handleTopLevelType(
topLevelType,
responderEvent,
eventComponentInstance,
false,
);
}
node = node.return;
}
node = node.return;
}
// Handle root level events
const rootEventInstances = rootEventTypesToEventComponentInstances.get(
topLevelType,
);
if (rootEventInstances !== undefined) {
const rootEventComponentInstances = Array.from(rootEventInstances);

for (let i = 0; i < rootEventComponentInstances.length; i++) {
const rootEventComponentInstance = rootEventComponentInstances[i];
handleTopLevelType(
topLevelType,
responderEvent,
rootEventComponentInstance,
true,
);
// Handle root level events
const rootEventInstances = rootEventTypesToEventComponentInstances.get(
topLevelType,
);
if (rootEventInstances !== undefined) {
const rootEventComponentInstances = Array.from(rootEventInstances);

for (let i = 0; i < rootEventComponentInstances.length; i++) {
const rootEventComponentInstance = rootEventComponentInstances[i];
handleTopLevelType(
topLevelType,
responderEvent,
rootEventComponentInstance,
true,
);
}
}
processEventQueue();
} finally {
currentTimers = null;
currentInstance = null;
currentEventQueue = null;
}
processEventQueue();
currentTimers = null;
}
}

function triggerOwnershipListeners(): void {
const listeningInstances = Array.from(ownershipChangeListeners);
const previousInstance = currentInstance;
for (let i = 0; i < listeningInstances.length; i++) {
const instance = listeningInstances[i];
const {props, responder, state} = instance;
currentInstance = instance;
try {
try {
for (let i = 0; i < listeningInstances.length; i++) {
const instance = listeningInstances[i];
const {props, responder, state} = instance;
currentInstance = instance;
responder.onOwnershipChange(eventResponderContext, props, state);
} finally {
currentInstance = previousInstance;
}
} finally {
currentInstance = previousInstance;
}
}

Expand All @@ -569,15 +595,13 @@ export function unmountEventResponder(
const onUnmount = responder.onUnmount;
if (onUnmount !== undefined) {
let {props, state} = eventComponentInstance;
const previousEventQueue = currentEventQueue;
const previousInstance = currentInstance;
currentEventQueue = createEventQueue();
currentInstance = eventComponentInstance;
try {
onUnmount(eventResponderContext, props, state);
} finally {
currentEventQueue = previousEventQueue;
currentInstance = previousInstance;
currentEventQueue = null;
currentInstance = null;
currentTimers = null;
}
}
Expand All @@ -589,3 +613,11 @@ export function unmountEventResponder(
ownershipChangeListeners.delete(eventComponentInstance);
}
}

function validateResponderContext(): void {
invariant(
currentEventQueue && currentInstance,
'An event responder context was used outside of an event cycle. ' +
'Use context.setTimeout() to use asynchronous responder context outside of event cycle .',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

);
}
Loading