[Flare] Refinements to useEvent hook (#15955)

This commit is contained in:
Dominic Gannaway
2019-06-21 23:10:55 +01:00
committed by GitHub
parent 696609d49a
commit dc298fdf91
8 changed files with 73 additions and 56 deletions
+35 -29
View File
@@ -84,6 +84,7 @@ type ResponderTimeout = {|
|};
type ResponderTimer = {|
isHook: boolean,
instance: ReactDOMEventComponentInstance,
func: () => void,
id: number,
@@ -117,6 +118,7 @@ let currentInstance: null | ReactDOMEventComponentInstance = null;
let currentEventQueue: null | EventQueue = null;
let currentTimerIDCounter = 0;
let currentDocument: null | Document = null;
let currentlyInHook = false;
const eventResponderContext: ReactDOMResponderContext = {
dispatchEvent(
@@ -366,6 +368,7 @@ const eventResponderContext: ReactDOMResponderContext = {
currentTimers.set(delay, timeout);
}
timeout.timers.set(timerId, {
isHook: currentlyInHook,
instance: ((currentInstance: any): ReactDOMEventComponentInstance),
func,
id: timerId,
@@ -456,6 +459,9 @@ const eventResponderContext: ReactDOMResponderContext = {
validateResponderContext();
continueLocalPropagation = true;
},
isRespondingToHook() {
return currentlyInHook;
},
};
function collectFocusableElements(
@@ -535,9 +541,10 @@ function processTimers(
currentEventQueue = createEventQueue();
try {
for (let i = 0; i < timersArr.length; i++) {
const {instance, func, id, timeStamp} = timersArr[i];
const {isHook, instance, func, id, timeStamp} = timersArr[i];
currentInstance = instance;
currentTimeStamp = timeStamp + delay;
currentlyInHook = isHook;
try {
func();
} finally {
@@ -763,9 +770,9 @@ function shouldSkipEventComponent(
eventResponderInstance: ReactDOMEventComponentInstance,
responder: ReactDOMEventResponder,
propagatedEventResponders: null | Set<ReactDOMEventResponder>,
localPropagation: boolean,
isHook: boolean,
): boolean {
if (propagatedEventResponders !== null && localPropagation) {
if (propagatedEventResponders !== null && !isHook) {
if (propagatedEventResponders.has(responder)) {
return true;
}
@@ -830,12 +837,7 @@ function traverseAndHandleEventResponderInstances(
// Capture target phase
for (i = length; i-- > 0; ) {
const targetEventResponderInstance = targetEventResponderInstances[i];
const {
localPropagation,
props,
responder,
state,
} = targetEventResponderInstance;
const {isHook, props, responder, state} = targetEventResponderInstance;
const eventListener = responder.onEventCapture;
if (eventListener !== undefined) {
if (
@@ -843,14 +845,15 @@ function traverseAndHandleEventResponderInstances(
targetEventResponderInstance,
((responder: any): ReactDOMEventResponder),
propagatedEventResponders,
localPropagation,
isHook,
)
) {
continue;
}
currentInstance = targetEventResponderInstance;
currentlyInHook = isHook;
eventListener(responderEvent, eventResponderContext, props, state);
if (localPropagation) {
if (!isHook) {
checkForLocalPropagationContinuation(
responder,
propagatedEventResponders,
@@ -863,12 +866,7 @@ function traverseAndHandleEventResponderInstances(
// Bubble target phase
for (i = 0; i < length; i++) {
const targetEventResponderInstance = targetEventResponderInstances[i];
const {
localPropagation,
props,
responder,
state,
} = targetEventResponderInstance;
const {isHook, props, responder, state} = targetEventResponderInstance;
const eventListener = responder.onEvent;
if (eventListener !== undefined) {
if (
@@ -876,14 +874,15 @@ function traverseAndHandleEventResponderInstances(
targetEventResponderInstance,
((responder: any): ReactDOMEventResponder),
propagatedEventResponders,
localPropagation,
isHook,
)
) {
continue;
}
currentInstance = targetEventResponderInstance;
currentlyInHook = isHook;
eventListener(responderEvent, eventResponderContext, props, state);
if (localPropagation) {
if (!isHook) {
checkForLocalPropagationContinuation(
responder,
propagatedEventResponders,
@@ -900,12 +899,7 @@ function traverseAndHandleEventResponderInstances(
if (length > 0) {
for (i = 0; i < length; i++) {
const rootEventResponderInstance = rootEventResponderInstances[i];
const {
localPropagation,
props,
responder,
state,
} = rootEventResponderInstance;
const {isHook, props, responder, state} = rootEventResponderInstance;
const eventListener = responder.onRootEvent;
if (eventListener !== undefined) {
if (
@@ -913,12 +907,13 @@ function traverseAndHandleEventResponderInstances(
rootEventResponderInstance,
responder,
null,
localPropagation,
isHook,
)
) {
continue;
}
currentInstance = rootEventResponderInstance;
currentlyInHook = isHook;
eventListener(responderEvent, eventResponderContext, props, state);
}
}
@@ -928,19 +923,24 @@ function traverseAndHandleEventResponderInstances(
function triggerOwnershipListeners(): void {
const listeningInstances = Array.from(ownershipChangeListeners);
const previousInstance = currentInstance;
const previouslyInHook = currentlyInHook;
currentEventQueue = createEventQueue();
try {
for (let i = 0; i < listeningInstances.length; i++) {
const instance = listeningInstances[i];
const {props, responder, state} = instance;
const {isHook, props, responder, state} = instance;
currentInstance = instance;
currentlyInHook = isHook;
const onOwnershipChange = ((responder: any): ReactDOMEventResponder)
.onOwnershipChange;
if (onOwnershipChange !== undefined) {
onOwnershipChange(eventResponderContext, props, state);
}
}
processEventQueue();
} finally {
currentInstance = previousInstance;
currentlyInHook = previouslyInHook;
}
}
@@ -953,11 +953,13 @@ export function mountEventResponder(
}
const onMount = responder.onMount;
if (onMount !== undefined) {
let {props, state} = eventComponentInstance;
let {isHook, props, state} = eventComponentInstance;
currentEventQueue = createEventQueue();
currentInstance = eventComponentInstance;
currentlyInHook = isHook;
try {
onMount(eventResponderContext, props, state);
processEventQueue();
} finally {
currentEventQueue = null;
currentInstance = null;
@@ -972,11 +974,13 @@ export function unmountEventResponder(
const responder = ((eventComponentInstance.responder: any): ReactDOMEventResponder);
const onUnmount = responder.onUnmount;
if (onUnmount !== undefined) {
let {props, state} = eventComponentInstance;
let {isHook, props, state} = eventComponentInstance;
currentEventQueue = createEventQueue();
currentInstance = eventComponentInstance;
currentlyInHook = isHook;
try {
onUnmount(eventResponderContext, props, state);
processEventQueue();
} finally {
currentEventQueue = null;
currentInstance = null;
@@ -1030,6 +1034,7 @@ export function dispatchEventForResponderEventSystem(
const previousTimers = currentTimers;
const previousTimeStamp = currentTimeStamp;
const previousDocument = currentDocument;
const previouslyInHook = currentlyInHook;
currentTimers = null;
currentEventQueue = createEventQueue();
currentDocument = (nativeEventTarget: any).ownerDocument;
@@ -1050,6 +1055,7 @@ export function dispatchEventForResponderEventSystem(
currentEventQueue = previousEventQueue;
currentTimeStamp = previousTimeStamp;
currentDocument = previousDocument;
currentlyInHook = previouslyInHook;
}
}
}
@@ -1067,11 +1067,12 @@ describe('DOMEventResponderSystem', () => {
};
context.dispatchEvent(fooEvent, props.onFoo, DiscreteEvent);
}
eventLogs.push(context.isRespondingToHook() ? '[hook]' : '[component]');
},
});
const Test = () => {
React.unstable_useEvent(EventComponent.responder, {
React.unstable_useEvent(EventComponent, {
onFoo: e => eventLogs.push('hook'),
});
return (
@@ -1083,13 +1084,13 @@ describe('DOMEventResponderSystem', () => {
ReactDOM.render(<Test />, container);
buttonRef.current.dispatchEvent(createEvent('foo'));
expect(eventLogs).toEqual(['prop', 'hook']);
expect(eventLogs).toEqual(['[component]', '[hook]', 'prop', 'hook']);
// Clear events
eventLogs.length = 0;
const Test2 = () => {
React.unstable_useEvent(EventComponent.responder, {
React.unstable_useEvent(EventComponent, {
onFoo: e => eventLogs.push('hook'),
});
return <button ref={buttonRef} />;
@@ -1101,10 +1102,10 @@ describe('DOMEventResponderSystem', () => {
expect(eventLogs).toEqual([]);
const Test3 = () => {
React.unstable_useEvent(EventComponent.responder, {
React.unstable_useEvent(EventComponent, {
onFoo: e => eventLogs.push('hook 2a'),
});
React.unstable_useEvent(EventComponent.responder, {
React.unstable_useEvent(EventComponent, {
onFoo: e => eventLogs.push('hook 2b'),
});
return (
@@ -1118,6 +1119,13 @@ describe('DOMEventResponderSystem', () => {
ReactDOM.render(<Test3 />, container);
buttonRef.current.dispatchEvent(createEvent('foo'));
expect(eventLogs).toEqual(['prop 2', 'hook 2a', 'hook 2b']);
expect(eventLogs).toEqual([
'[component]',
'[hook]',
'[hook]',
'prop 2',
'hook 2a',
'hook 2b',
]);
});
});
+1 -1
View File
@@ -1041,7 +1041,7 @@ function completeWork(
responder,
rootContainerInstance,
responderState || {},
true,
false,
);
markUpdate(workInProgress);
} else {
+6 -4
View File
@@ -9,6 +9,7 @@
import type {Fiber, Dependencies} from './ReactFiber';
import type {
ReactEventComponent,
ReactEventResponder,
ReactEventComponentInstance,
} from 'shared/ReactTypes';
@@ -32,9 +33,10 @@ export function prepareToReadEventComponents(workInProgress: Fiber): void {
}
export function updateEventComponentInstance<T, E, C>(
responder: ReactEventResponder<T, E, C>,
eventComponent: ReactEventComponent<T, E, C>,
props: Object,
): void {
const responder = eventComponent.responder;
invariant(
responder.allowEventHooks,
'The "%s" event responder cannot be used via the "useEvent" hook.',
@@ -67,7 +69,7 @@ export function updateEventComponentInstance<T, E, C>(
responder,
null,
responderState || {},
false,
true,
);
events.push(eventComponentInstance);
currentEventComponentInstanceIndex++;
@@ -85,11 +87,11 @@ export function createEventComponentInstance<T, E, C>(
responder: ReactEventResponder<T, E, C>,
rootInstance: mixed,
state: Object,
localPropagation: boolean,
isHook: boolean,
): ReactEventComponentInstance<T, E, C> {
return {
currentFiber,
localPropagation,
isHook,
props,
responder,
rootEventTypes: null,
+12 -12
View File
@@ -7,7 +7,7 @@
* @flow
*/
import type {ReactEventResponder, ReactContext} from 'shared/ReactTypes';
import type {ReactEventComponent, ReactContext} from 'shared/ReactTypes';
import type {SideEffectTag} from 'shared/ReactSideEffectTags';
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -83,7 +83,7 @@ export type Dispatcher = {
): void,
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
useEvent<T, E, C>(
responder: ReactEventResponder<T, E, C>,
eventComponent: ReactEventComponent<T, E, C>,
props: Object,
): void,
};
@@ -1399,10 +1399,10 @@ if (__DEV__) {
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
currentHookNameInDev = 'useEvent';
mountHookTypesDev();
updateEventComponentInstance(responder, props);
updateEventComponentInstance(eventComponent, props);
},
};
@@ -1501,10 +1501,10 @@ if (__DEV__) {
updateHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
currentHookNameInDev = 'useEvent';
updateHookTypesDev();
updateEventComponentInstance(responder, props);
updateEventComponentInstance(eventComponent, props);
},
};
@@ -1603,10 +1603,10 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
currentHookNameInDev = 'useEvent';
updateHookTypesDev();
updateEventComponentInstance(responder, props);
updateEventComponentInstance(eventComponent, props);
},
};
@@ -1716,11 +1716,11 @@ if (__DEV__) {
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
currentHookNameInDev = 'useEvent';
warnInvalidHookAccess();
mountHookTypesDev();
updateEventComponentInstance(responder, props);
updateEventComponentInstance(eventComponent, props);
},
};
@@ -1830,11 +1830,11 @@ if (__DEV__) {
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
currentHookNameInDev = 'useEvent';
warnInvalidHookAccess();
updateHookTypesDev();
updateEventComponentInstance(responder, props);
updateEventComponentInstance(eventComponent, props);
},
};
}
+3 -3
View File
@@ -7,7 +7,7 @@
* @flow
*/
import type {ReactEventResponder, ReactContext} from 'shared/ReactTypes';
import type {ReactEventComponent, ReactContext} from 'shared/ReactTypes';
import invariant from 'shared/invariant';
import warning from 'shared/warning';
@@ -139,9 +139,9 @@ export function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
export const emptyObject = {};
export function useEvent<T, E, C>(
responder: ReactEventResponder<T, E, C>,
eventComponent: ReactEventComponent<T, E, C>,
props: null | Object,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useEvent(responder, props || emptyObject);
return dispatcher.useEvent(eventComponent, props || emptyObject);
}
+1
View File
@@ -84,4 +84,5 @@ export type ReactDOMResponderContext = {
deep: boolean,
) => boolean,
continueLocalPropagation(): void,
isRespondingToHook(): boolean,
};
+1 -1
View File
@@ -83,7 +83,7 @@ export type RefObject = {|
export type ReactEventComponentInstance<T, E, C> = {|
currentFiber: mixed,
localPropagation: boolean,
isHook: boolean,
props: Object,
responder: ReactEventResponder<T, E, C>,
rootEventTypes: null | Set<string>,