mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ReactDOM.useEvent: Add more scaffolding for useEvent hook (#18271)
This commit is contained in:
@@ -39,6 +39,11 @@ type HookLogEntry = {
|
||||
...
|
||||
};
|
||||
|
||||
type ReactDebugListenerMap = {|
|
||||
clear: () => void,
|
||||
setListener: (instance: EventTarget, callback: ?(Event) => void) => void,
|
||||
|};
|
||||
|
||||
let hookLog: Array<HookLogEntry> = [];
|
||||
|
||||
// Primitives
|
||||
@@ -256,6 +261,16 @@ function useTransition(
|
||||
return [callback => {}, false];
|
||||
}
|
||||
|
||||
const noOp = () => {};
|
||||
|
||||
function useEvent(event: any): ReactDebugListenerMap {
|
||||
hookLog.push({primitive: 'Event', stackError: new Error(), value: event});
|
||||
return {
|
||||
clear: noOp,
|
||||
setListener: noOp,
|
||||
};
|
||||
}
|
||||
|
||||
function useDeferredValue<T>(value: T, config: TimeoutConfig | null | void): T {
|
||||
// useDeferredValue() composes multiple hooks internally.
|
||||
// Advance the current hook index the same number of times
|
||||
@@ -285,6 +300,7 @@ const Dispatcher: DispatcherType = {
|
||||
useResponder,
|
||||
useTransition,
|
||||
useDeferredValue,
|
||||
useEvent,
|
||||
};
|
||||
|
||||
// Inspect
|
||||
|
||||
@@ -51,6 +51,9 @@ import type {
|
||||
ReactDOMEventResponder,
|
||||
ReactDOMEventResponderInstance,
|
||||
ReactDOMFundamentalComponentInstance,
|
||||
ReactDOMListener,
|
||||
ReactDOMListenerEvent,
|
||||
ReactDOMListenerMap,
|
||||
} from 'shared/ReactDOMTypes';
|
||||
import {
|
||||
mountEventResponder,
|
||||
@@ -70,6 +73,10 @@ import {
|
||||
IS_PASSIVE,
|
||||
} from 'legacy-events/EventSystemFlags';
|
||||
|
||||
export type ReactListenerEvent = ReactDOMListenerEvent;
|
||||
export type ReactListenerMap = ReactDOMListenerMap;
|
||||
export type ReactListener = ReactDOMListener;
|
||||
|
||||
export type Type = string;
|
||||
export type Props = {
|
||||
autoFocus?: boolean,
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {PluginModule} from 'legacy-events/PluginModuleType';
|
||||
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
|
||||
import type {ReactDOMListener} from 'shared/ReactDOMTypes';
|
||||
|
||||
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
|
||||
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
|
||||
@@ -296,3 +297,11 @@ export function dispatchEventForPluginEventSystem(
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function attachElementListener(listener: ReactDOMListener): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function detachElementListener(listener: ReactDOMListener): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@@ -1039,4 +1039,40 @@ describe('DOMModernPluginEventSystem', () => {
|
||||
expect(log).toEqual([]);
|
||||
expect(onDivClick).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
describe('ReactDOM.useEvent', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableModernEventSystem = true;
|
||||
ReactFeatureFlags.enableUseEventAPI = true;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
});
|
||||
|
||||
if (!__EXPERIMENTAL__) {
|
||||
it("empty test so Jest doesn't complain", () => {});
|
||||
return;
|
||||
}
|
||||
|
||||
it('should create the same event listener map', () => {
|
||||
let listenerMaps = [];
|
||||
|
||||
function Test() {
|
||||
const listenerMap = ReactDOM.unstable_useEvent('click');
|
||||
|
||||
listenerMaps.push(listenerMap);
|
||||
|
||||
return <div />;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
ReactDOM.render(<Test />, container);
|
||||
expect(listenerMaps.length).toEqual(2);
|
||||
expect(listenerMaps[0]).toEqual(listenerMaps[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@ import type {
|
||||
ReactEventResponderListener,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {SuspenseConfig} from 'react-reconciler/src/ReactFiberSuspenseConfig';
|
||||
import type {ReactDOMListenerMap} from 'shared/ReactDOMTypes';
|
||||
|
||||
import {validateContextBounds} from './ReactPartialRendererContext';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
@@ -474,6 +476,13 @@ function useTransition(
|
||||
return [startTransition, false];
|
||||
}
|
||||
|
||||
function useEvent(event: any): ReactDOMListenerMap {
|
||||
return {
|
||||
clear: noop,
|
||||
setListener: noop,
|
||||
};
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
export let currentThreadID: ThreadID = 0;
|
||||
@@ -500,4 +509,5 @@ export const Dispatcher: DispatcherType = {
|
||||
useResponder,
|
||||
useDeferredValue,
|
||||
useTransition,
|
||||
useEvent,
|
||||
};
|
||||
|
||||
@@ -76,6 +76,10 @@ export type UpdatePayload = Object;
|
||||
export type TimeoutHandle = TimeoutID;
|
||||
export type NoTimeout = -1;
|
||||
|
||||
export type ReactListenerEvent = Object;
|
||||
export type ReactListenerMap = Object;
|
||||
export type ReactListener = Object;
|
||||
|
||||
// TODO: Remove this conditional once all changes have propagated.
|
||||
if (registerEventHandler) {
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,10 @@ import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
|
||||
|
||||
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
|
||||
|
||||
export type ReactListenerEvent = Object;
|
||||
export type ReactListenerMap = Object;
|
||||
export type ReactListener = Object;
|
||||
|
||||
export type Type = string;
|
||||
export type Props = Object;
|
||||
export type Container = number;
|
||||
|
||||
+122
-1
@@ -17,8 +17,13 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {HookEffectTag} from './ReactHookEffectTags';
|
||||
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
|
||||
import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
|
||||
import type {
|
||||
ReactListenerEvent,
|
||||
ReactListenerMap,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {NoWork, Sync} from './ReactFiberExpirationTime';
|
||||
import {readContext} from './ReactFiberNewContext';
|
||||
@@ -28,6 +33,7 @@ import {
|
||||
Passive as PassiveEffect,
|
||||
} from 'shared/ReactSideEffectTags';
|
||||
import {
|
||||
NoEffect as NoHookEffect,
|
||||
HasEffect as HookHasEffect,
|
||||
Layout as HookLayout,
|
||||
Passive as HookPassive,
|
||||
@@ -97,6 +103,7 @@ export type Dispatcher = {|
|
||||
useTransition(
|
||||
config: SuspenseConfig | void | null,
|
||||
): [(() => void) => void, boolean],
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap,
|
||||
|};
|
||||
|
||||
type Update<S, A> = {|
|
||||
@@ -129,7 +136,8 @@ export type HookType =
|
||||
| 'useDebugValue'
|
||||
| 'useResponder'
|
||||
| 'useDeferredValue'
|
||||
| 'useTransition';
|
||||
| 'useTransition'
|
||||
| 'useEvent';
|
||||
|
||||
let didWarnAboutMismatchedHooksForComponent;
|
||||
if (__DEV__) {
|
||||
@@ -1369,6 +1377,77 @@ function dispatchAction<S, A>(
|
||||
}
|
||||
}
|
||||
|
||||
const noOpMount = () => {};
|
||||
|
||||
function mountEventListener(event: ReactListenerEvent): ReactListenerMap {
|
||||
if (enableUseEventAPI) {
|
||||
const hook = mountWorkInProgressHook();
|
||||
|
||||
const clear = () => {
|
||||
// TODO
|
||||
};
|
||||
|
||||
const reactListenerMap: ReactListenerMap = {
|
||||
clear,
|
||||
setListener(instance: EventTarget, callback: ?(Event) => void): void {
|
||||
// TODO
|
||||
},
|
||||
};
|
||||
// In order to clear up upon the hook unmounting,
|
||||
// we ensure we set the effecrt tag so that we visit
|
||||
// this effect in the commit phase, so we can handle
|
||||
// clean-up accordingly.
|
||||
currentlyRenderingFiber.effectTag |= UpdateEffect;
|
||||
pushEffect(NoHookEffect, noOpMount, clear, null);
|
||||
hook.memoizedState = [reactListenerMap, event, clear];
|
||||
return reactListenerMap;
|
||||
}
|
||||
// To make Flow not complain
|
||||
return (undefined: any);
|
||||
}
|
||||
|
||||
function updateEventListener(event: ReactListenerEvent): ReactListenerMap {
|
||||
if (enableUseEventAPI) {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const [reactListenerMap, memoizedEvent, clear] = hook.memoizedState;
|
||||
if (__DEV__) {
|
||||
if (memoizedEvent.type !== event.type) {
|
||||
console.warn(
|
||||
'The event type argument passed to the useEvent() hook was different between renders.' +
|
||||
' The event type is static and should never change between renders.',
|
||||
);
|
||||
}
|
||||
if (memoizedEvent.capture !== event.capture) {
|
||||
console.warn(
|
||||
'The "capture" option passed to the useEvent() hook was different between renders.' +
|
||||
' The "capture" option is static and should never change between renders.',
|
||||
);
|
||||
}
|
||||
if (memoizedEvent.priority !== event.priority) {
|
||||
console.warn(
|
||||
'The "priority" option passed to the useEvent() hook was different between renders.' +
|
||||
' The "priority" option is static and should never change between renders.',
|
||||
);
|
||||
}
|
||||
if (memoizedEvent.passive !== event.passive) {
|
||||
console.warn(
|
||||
'The "passive" option passed to the useEvent() hook was different between renders.' +
|
||||
' The "passive" option is static and should never change between renders.',
|
||||
);
|
||||
}
|
||||
}
|
||||
// In order to clear up upon the hook unmounting,
|
||||
// we ensure we set the effecrt tag so that we visit
|
||||
// this effect in the commit phase, so we can handle
|
||||
// clean-up accordingly.
|
||||
currentlyRenderingFiber.effectTag |= UpdateEffect;
|
||||
pushEffect(NoHookEffect, noOpMount, clear, null);
|
||||
return reactListenerMap;
|
||||
}
|
||||
// To make Flow not complain
|
||||
return (undefined: any);
|
||||
}
|
||||
|
||||
export const ContextOnlyDispatcher: Dispatcher = {
|
||||
readContext,
|
||||
|
||||
@@ -1385,6 +1464,7 @@ export const ContextOnlyDispatcher: Dispatcher = {
|
||||
useResponder: throwInvalidHookError,
|
||||
useDeferredValue: throwInvalidHookError,
|
||||
useTransition: throwInvalidHookError,
|
||||
useEvent: throwInvalidHookError,
|
||||
};
|
||||
|
||||
const HooksDispatcherOnMount: Dispatcher = {
|
||||
@@ -1403,6 +1483,7 @@ const HooksDispatcherOnMount: Dispatcher = {
|
||||
useResponder: createDeprecatedResponderListener,
|
||||
useDeferredValue: mountDeferredValue,
|
||||
useTransition: mountTransition,
|
||||
useEvent: mountEventListener,
|
||||
};
|
||||
|
||||
const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
@@ -1421,6 +1502,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
useResponder: createDeprecatedResponderListener,
|
||||
useDeferredValue: updateDeferredValue,
|
||||
useTransition: updateTransition,
|
||||
useEvent: updateEventListener,
|
||||
};
|
||||
|
||||
const HooksDispatcherOnRerender: Dispatcher = {
|
||||
@@ -1439,6 +1521,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
|
||||
useResponder: createDeprecatedResponderListener,
|
||||
useDeferredValue: rerenderDeferredValue,
|
||||
useTransition: rerenderTransition,
|
||||
useEvent: updateEventListener,
|
||||
};
|
||||
|
||||
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
@@ -1588,6 +1671,11 @@ if (__DEV__) {
|
||||
mountHookTypesDev();
|
||||
return mountTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
mountHookTypesDev();
|
||||
return mountEventListener(event);
|
||||
},
|
||||
};
|
||||
|
||||
HooksDispatcherOnMountWithHookTypesInDEV = {
|
||||
@@ -1705,6 +1793,11 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return mountTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return mountEventListener(event);
|
||||
},
|
||||
};
|
||||
|
||||
HooksDispatcherOnUpdateInDEV = {
|
||||
@@ -1822,6 +1915,11 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return updateEventListener(event);
|
||||
},
|
||||
};
|
||||
|
||||
HooksDispatcherOnRerenderInDEV = {
|
||||
@@ -1939,6 +2037,11 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return rerenderTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return updateEventListener(event);
|
||||
},
|
||||
};
|
||||
|
||||
InvalidNestedHooksDispatcherOnMountInDEV = {
|
||||
@@ -2070,6 +2173,12 @@ if (__DEV__) {
|
||||
mountHookTypesDev();
|
||||
return mountTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountEventListener(event);
|
||||
},
|
||||
};
|
||||
|
||||
InvalidNestedHooksDispatcherOnUpdateInDEV = {
|
||||
@@ -2201,6 +2310,12 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return updateTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateEventListener(event);
|
||||
},
|
||||
};
|
||||
|
||||
InvalidNestedHooksDispatcherOnRerenderInDEV = {
|
||||
@@ -2332,5 +2447,11 @@ if (__DEV__) {
|
||||
updateHookTypesDev();
|
||||
return rerenderTransition(config);
|
||||
},
|
||||
useEvent(event: ReactListenerEvent): ReactListenerMap {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateEventListener(event);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ export opaque type ChildSet = mixed; // eslint-disable-line no-undef
|
||||
export opaque type TimeoutHandle = mixed; // eslint-disable-line no-undef
|
||||
export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
|
||||
export type EventResponder = any;
|
||||
export type ReactListenerEvent = Object;
|
||||
export type ReactListenerMap = Object;
|
||||
export type ReactListener = Object;
|
||||
|
||||
export const getPublicInstance = $$$hostConfig.getPublicInstance;
|
||||
export const getRootHostContext = $$$hostConfig.getRootHostContext;
|
||||
|
||||
@@ -45,6 +45,10 @@ export type TimeoutHandle = TimeoutID;
|
||||
export type NoTimeout = -1;
|
||||
export type EventResponder = any;
|
||||
|
||||
export type ReactListenerEvent = Object;
|
||||
export type ReactListenerMap = Object;
|
||||
export type ReactListener = Object;
|
||||
|
||||
export * from 'shared/HostConfigWithNoPersistence';
|
||||
export * from 'shared/HostConfigWithNoHydration';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user