mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Flare] Refinements to useEvent hook (#15955)
This commit is contained in:
+35
-29
@@ -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
@@ -1041,7 +1041,7 @@ function completeWork(
|
||||
responder,
|
||||
rootContainerInstance,
|
||||
responderState || {},
|
||||
true,
|
||||
false,
|
||||
);
|
||||
markUpdate(workInProgress);
|
||||
} else {
|
||||
|
||||
+6
-4
@@ -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
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -84,4 +84,5 @@ export type ReactDOMResponderContext = {
|
||||
deep: boolean,
|
||||
) => boolean,
|
||||
continueLocalPropagation(): void,
|
||||
isRespondingToHook(): boolean,
|
||||
};
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user