mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Refactor useEvent (#25336)
* Refactor useEvent Previously, the useEvent implementation made use of effect infra under the hood. This was a lot of extra overhead for functionality we didn't use (events have no deps, and no clean up functions). This PR refactors the implementation to instead use a queue to ensure that the callback is stable across renders. Additionally, the function signature was updated to infer the callback's argument types and return value. While this doesn't affect anything internal it more accurately describes what's being passed.
This commit is contained in:
@@ -16,7 +16,7 @@ import type {
|
||||
UpdatePayload,
|
||||
} from './ReactFiberHostConfig';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {FiberRoot} from './ReactInternalTypes';
|
||||
import type {FiberRoot, EventFunctionWrapper} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane.new';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
|
||||
@@ -164,7 +164,6 @@ import {
|
||||
Layout as HookLayout,
|
||||
Insertion as HookInsertion,
|
||||
Passive as HookPassive,
|
||||
Snapshot as HookSnapshot,
|
||||
} from './ReactHookEffectTags';
|
||||
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
|
||||
import {doesFiberContain} from './ReactFiberTreeReflection';
|
||||
@@ -416,8 +415,7 @@ function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
|
||||
case FunctionComponent: {
|
||||
if (enableUseEventHook) {
|
||||
if ((flags & Update) !== NoFlags) {
|
||||
// useEvent doesn't need to be cleaned up
|
||||
commitHookEffectListMount(HookSnapshot | HookHasEffect, finishedWork);
|
||||
commitUseEventMount(finishedWork);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -665,6 +663,21 @@ function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
|
||||
}
|
||||
}
|
||||
|
||||
function commitUseEventMount(finishedWork: Fiber) {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
|
||||
const eventPayloads = updateQueue !== null ? updateQueue.events : null;
|
||||
if (eventPayloads !== null) {
|
||||
// FunctionComponentUpdateQueue.events is a flat array of
|
||||
// [EventFunctionWrapper, EventFunction, ...], so increment by 2 each iteration to find the next
|
||||
// pair.
|
||||
for (let ii = 0; ii < eventPayloads.length; ii += 2) {
|
||||
const eventFn: EventFunctionWrapper<any, any, any> = eventPayloads[ii];
|
||||
const nextImpl = eventPayloads[ii + 1];
|
||||
eventFn._impl = nextImpl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function commitPassiveEffectDurations(
|
||||
finishedRoot: FiberRoot,
|
||||
finishedWork: Fiber,
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
UpdatePayload,
|
||||
} from './ReactFiberHostConfig';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {FiberRoot} from './ReactInternalTypes';
|
||||
import type {FiberRoot, EventFunctionWrapper} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane.old';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
|
||||
@@ -164,7 +164,6 @@ import {
|
||||
Layout as HookLayout,
|
||||
Insertion as HookInsertion,
|
||||
Passive as HookPassive,
|
||||
Snapshot as HookSnapshot,
|
||||
} from './ReactHookEffectTags';
|
||||
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
|
||||
import {doesFiberContain} from './ReactFiberTreeReflection';
|
||||
@@ -416,8 +415,7 @@ function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
|
||||
case FunctionComponent: {
|
||||
if (enableUseEventHook) {
|
||||
if ((flags & Update) !== NoFlags) {
|
||||
// useEvent doesn't need to be cleaned up
|
||||
commitHookEffectListMount(HookSnapshot | HookHasEffect, finishedWork);
|
||||
commitUseEventMount(finishedWork);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -665,6 +663,21 @@ function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
|
||||
}
|
||||
}
|
||||
|
||||
function commitUseEventMount(finishedWork: Fiber) {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
|
||||
const eventPayloads = updateQueue !== null ? updateQueue.events : null;
|
||||
if (eventPayloads !== null) {
|
||||
// FunctionComponentUpdateQueue.events is a flat array of
|
||||
// [EventFunctionWrapper, EventFunction, ...], so increment by 2 each iteration to find the next
|
||||
// pair.
|
||||
for (let ii = 0; ii < eventPayloads.length; ii += 2) {
|
||||
const eventFn: EventFunctionWrapper<any, any, any> = eventPayloads[ii];
|
||||
const nextImpl = eventPayloads[ii + 1];
|
||||
eventFn._impl = nextImpl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function commitPassiveEffectDurations(
|
||||
finishedRoot: FiberRoot,
|
||||
finishedWork: Fiber,
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
Dispatcher,
|
||||
HookType,
|
||||
MemoCache,
|
||||
EventFunctionWrapper,
|
||||
} from './ReactInternalTypes';
|
||||
import type {Lanes, Lane} from './ReactFiberLane.new';
|
||||
import type {HookFlags} from './ReactHookEffectTags';
|
||||
@@ -86,7 +87,6 @@ import {
|
||||
Layout as HookLayout,
|
||||
Passive as HookPassive,
|
||||
Insertion as HookInsertion,
|
||||
Snapshot as HookSnapshot,
|
||||
} from './ReactHookEffectTags';
|
||||
import {
|
||||
getWorkInProgressRoot,
|
||||
@@ -184,6 +184,7 @@ type StoreConsistencyCheck<T> = {
|
||||
|
||||
export type FunctionComponentUpdateQueue = {
|
||||
lastEffect: Effect | null,
|
||||
events: Array<() => mixed> | null,
|
||||
stores: Array<StoreConsistencyCheck<any>> | null,
|
||||
// NOTE: optional, only set when enableUseMemoCacheHook is enabled
|
||||
memoCache?: MemoCache | null,
|
||||
@@ -727,6 +728,7 @@ if (enableUseMemoCacheHook) {
|
||||
createFunctionComponentUpdateQueue = () => {
|
||||
return {
|
||||
lastEffect: null,
|
||||
events: null,
|
||||
stores: null,
|
||||
memoCache: null,
|
||||
};
|
||||
@@ -735,6 +737,7 @@ if (enableUseMemoCacheHook) {
|
||||
createFunctionComponentUpdateQueue = () => {
|
||||
return {
|
||||
lastEffect: null,
|
||||
events: null,
|
||||
stores: null,
|
||||
};
|
||||
};
|
||||
@@ -1871,49 +1874,52 @@ function updateEffect(
|
||||
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
}
|
||||
|
||||
function mountEvent<T>(callback: () => T): () => T {
|
||||
const hook = mountWorkInProgressHook();
|
||||
const ref = {current: callback};
|
||||
function useEventImpl<Args, Return, F: (...Array<Args>) => Return>(
|
||||
event: EventFunctionWrapper<Args, Return, F>,
|
||||
nextImpl: F,
|
||||
) {
|
||||
currentlyRenderingFiber.flags |= UpdateEffect;
|
||||
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
|
||||
if (componentUpdateQueue === null) {
|
||||
componentUpdateQueue = createFunctionComponentUpdateQueue();
|
||||
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
|
||||
componentUpdateQueue.events = [event, nextImpl];
|
||||
} else {
|
||||
const events = componentUpdateQueue.events;
|
||||
if (events === null) {
|
||||
componentUpdateQueue.events = [event, nextImpl];
|
||||
} else {
|
||||
events.push(event, nextImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function event() {
|
||||
function mountEvent<Args, Return, F: (...Array<Args>) => Return>(
|
||||
callback: F,
|
||||
): EventFunctionWrapper<Args, Return, F> {
|
||||
const hook = mountWorkInProgressHook();
|
||||
const eventFn: EventFunctionWrapper<Args, Return, F> = function eventFn() {
|
||||
if (isInvalidExecutionContextForEventFunction()) {
|
||||
throw new Error(
|
||||
"A function wrapped in useEvent can't be called during rendering.",
|
||||
);
|
||||
}
|
||||
return ref.current.apply(undefined, arguments);
|
||||
}
|
||||
return eventFn._impl.apply(undefined, arguments);
|
||||
};
|
||||
eventFn._impl = callback;
|
||||
|
||||
// TODO: We don't need all the overhead of an effect object since there are no deps and no
|
||||
// clean up functions.
|
||||
mountEffectImpl(
|
||||
UpdateEffect,
|
||||
HookSnapshot,
|
||||
() => {
|
||||
ref.current = callback;
|
||||
},
|
||||
[ref, callback],
|
||||
);
|
||||
|
||||
hook.memoizedState = [ref, event];
|
||||
|
||||
return event;
|
||||
useEventImpl(eventFn, callback);
|
||||
hook.memoizedState = eventFn;
|
||||
return eventFn;
|
||||
}
|
||||
|
||||
function updateEvent<T>(callback: () => T): () => T {
|
||||
function updateEvent<Args, Return, F: (...Array<Args>) => Return>(
|
||||
callback: F,
|
||||
): EventFunctionWrapper<Args, Return, F> {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const ref = hook.memoizedState[0];
|
||||
|
||||
updateEffectImpl(
|
||||
UpdateEffect,
|
||||
HookSnapshot,
|
||||
() => {
|
||||
ref.current = callback;
|
||||
},
|
||||
[ref, callback],
|
||||
);
|
||||
|
||||
return hook.memoizedState[1];
|
||||
const eventFn = hook.memoizedState;
|
||||
useEventImpl(eventFn, callback);
|
||||
return eventFn;
|
||||
}
|
||||
|
||||
function mountInsertionEffect(
|
||||
@@ -2890,9 +2896,11 @@ if (__DEV__) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useMemoCache = useMemoCache;
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent<T>(
|
||||
callback: () => T,
|
||||
): () => T {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent<
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
mountHookTypesDev();
|
||||
return mountEvent(callback);
|
||||
@@ -3048,8 +3056,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return mountEvent(callback);
|
||||
@@ -3204,9 +3214,11 @@ if (__DEV__) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useMemoCache = useMemoCache;
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent<T>(
|
||||
callback: () => T,
|
||||
): () => T {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent<
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return updateEvent(callback);
|
||||
@@ -3363,8 +3375,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return updateEvent(callback);
|
||||
@@ -3547,8 +3561,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
@@ -3732,8 +3748,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
@@ -3918,8 +3936,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
Dispatcher,
|
||||
HookType,
|
||||
MemoCache,
|
||||
EventFunctionWrapper,
|
||||
} from './ReactInternalTypes';
|
||||
import type {Lanes, Lane} from './ReactFiberLane.old';
|
||||
import type {HookFlags} from './ReactHookEffectTags';
|
||||
@@ -86,7 +87,6 @@ import {
|
||||
Layout as HookLayout,
|
||||
Passive as HookPassive,
|
||||
Insertion as HookInsertion,
|
||||
Snapshot as HookSnapshot,
|
||||
} from './ReactHookEffectTags';
|
||||
import {
|
||||
getWorkInProgressRoot,
|
||||
@@ -184,6 +184,7 @@ type StoreConsistencyCheck<T> = {
|
||||
|
||||
export type FunctionComponentUpdateQueue = {
|
||||
lastEffect: Effect | null,
|
||||
events: Array<() => mixed> | null,
|
||||
stores: Array<StoreConsistencyCheck<any>> | null,
|
||||
// NOTE: optional, only set when enableUseMemoCacheHook is enabled
|
||||
memoCache?: MemoCache | null,
|
||||
@@ -727,6 +728,7 @@ if (enableUseMemoCacheHook) {
|
||||
createFunctionComponentUpdateQueue = () => {
|
||||
return {
|
||||
lastEffect: null,
|
||||
events: null,
|
||||
stores: null,
|
||||
memoCache: null,
|
||||
};
|
||||
@@ -735,6 +737,7 @@ if (enableUseMemoCacheHook) {
|
||||
createFunctionComponentUpdateQueue = () => {
|
||||
return {
|
||||
lastEffect: null,
|
||||
events: null,
|
||||
stores: null,
|
||||
};
|
||||
};
|
||||
@@ -1871,49 +1874,52 @@ function updateEffect(
|
||||
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
}
|
||||
|
||||
function mountEvent<T>(callback: () => T): () => T {
|
||||
const hook = mountWorkInProgressHook();
|
||||
const ref = {current: callback};
|
||||
function useEventImpl<Args, Return, F: (...Array<Args>) => Return>(
|
||||
event: EventFunctionWrapper<Args, Return, F>,
|
||||
nextImpl: F,
|
||||
) {
|
||||
currentlyRenderingFiber.flags |= UpdateEffect;
|
||||
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
|
||||
if (componentUpdateQueue === null) {
|
||||
componentUpdateQueue = createFunctionComponentUpdateQueue();
|
||||
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
|
||||
componentUpdateQueue.events = [event, nextImpl];
|
||||
} else {
|
||||
const events = componentUpdateQueue.events;
|
||||
if (events === null) {
|
||||
componentUpdateQueue.events = [event, nextImpl];
|
||||
} else {
|
||||
events.push(event, nextImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function event() {
|
||||
function mountEvent<Args, Return, F: (...Array<Args>) => Return>(
|
||||
callback: F,
|
||||
): EventFunctionWrapper<Args, Return, F> {
|
||||
const hook = mountWorkInProgressHook();
|
||||
const eventFn: EventFunctionWrapper<Args, Return, F> = function eventFn() {
|
||||
if (isInvalidExecutionContextForEventFunction()) {
|
||||
throw new Error(
|
||||
"A function wrapped in useEvent can't be called during rendering.",
|
||||
);
|
||||
}
|
||||
return ref.current.apply(undefined, arguments);
|
||||
}
|
||||
return eventFn._impl.apply(undefined, arguments);
|
||||
};
|
||||
eventFn._impl = callback;
|
||||
|
||||
// TODO: We don't need all the overhead of an effect object since there are no deps and no
|
||||
// clean up functions.
|
||||
mountEffectImpl(
|
||||
UpdateEffect,
|
||||
HookSnapshot,
|
||||
() => {
|
||||
ref.current = callback;
|
||||
},
|
||||
[ref, callback],
|
||||
);
|
||||
|
||||
hook.memoizedState = [ref, event];
|
||||
|
||||
return event;
|
||||
useEventImpl(eventFn, callback);
|
||||
hook.memoizedState = eventFn;
|
||||
return eventFn;
|
||||
}
|
||||
|
||||
function updateEvent<T>(callback: () => T): () => T {
|
||||
function updateEvent<Args, Return, F: (...Array<Args>) => Return>(
|
||||
callback: F,
|
||||
): EventFunctionWrapper<Args, Return, F> {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const ref = hook.memoizedState[0];
|
||||
|
||||
updateEffectImpl(
|
||||
UpdateEffect,
|
||||
HookSnapshot,
|
||||
() => {
|
||||
ref.current = callback;
|
||||
},
|
||||
[ref, callback],
|
||||
);
|
||||
|
||||
return hook.memoizedState[1];
|
||||
const eventFn = hook.memoizedState;
|
||||
useEventImpl(eventFn, callback);
|
||||
return eventFn;
|
||||
}
|
||||
|
||||
function mountInsertionEffect(
|
||||
@@ -2890,9 +2896,11 @@ if (__DEV__) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useMemoCache = useMemoCache;
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent<T>(
|
||||
callback: () => T,
|
||||
): () => T {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent<
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
mountHookTypesDev();
|
||||
return mountEvent(callback);
|
||||
@@ -3048,8 +3056,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return mountEvent(callback);
|
||||
@@ -3204,9 +3214,11 @@ if (__DEV__) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useMemoCache = useMemoCache;
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent<T>(
|
||||
callback: () => T,
|
||||
): () => T {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent<
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return updateEvent(callback);
|
||||
@@ -3363,8 +3375,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
updateHookTypesDev();
|
||||
return updateEvent(callback);
|
||||
@@ -3547,8 +3561,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
@@ -3732,8 +3748,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
@@ -3918,8 +3936,10 @@ if (__DEV__) {
|
||||
}
|
||||
if (enableUseEventHook) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEvent = function useEvent<
|
||||
T,
|
||||
>(callback: () => T): () => T {
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
>(callback: F): EventFunctionWrapper<Args, Return, F> {
|
||||
currentHookNameInDev = 'useEvent';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
|
||||
+5
-6
@@ -9,13 +9,12 @@
|
||||
|
||||
export type HookFlags = number;
|
||||
|
||||
export const NoFlags = /* */ 0b00000;
|
||||
export const NoFlags = /* */ 0b0000;
|
||||
|
||||
// Represents whether effect should fire.
|
||||
export const HasEffect = /* */ 0b00001;
|
||||
export const HasEffect = /* */ 0b0001;
|
||||
|
||||
// Represents the phase in which the effect (not the clean-up) fires.
|
||||
export const Snapshot = /* */ 0b00010;
|
||||
export const Insertion = /* */ 0b00100;
|
||||
export const Layout = /* */ 0b01000;
|
||||
export const Passive = /* */ 0b10000;
|
||||
export const Insertion = /* */ 0b0010;
|
||||
export const Layout = /* */ 0b0100;
|
||||
export const Passive = /* */ 0b1000;
|
||||
|
||||
+14
-1
@@ -286,6 +286,17 @@ type SuspenseCallbackOnlyFiberRootProperties = {
|
||||
hydrationCallbacks: null | SuspenseHydrationCallbacks,
|
||||
};
|
||||
|
||||
// A wrapper callable object around a useEvent callback that throws if the callback is called during
|
||||
// rendering. The _impl property points to the actual implementation.
|
||||
export type EventFunctionWrapper<
|
||||
Args,
|
||||
Return,
|
||||
F: (...Array<Args>) => Return,
|
||||
> = {
|
||||
(): F,
|
||||
_impl: F,
|
||||
};
|
||||
|
||||
export type TransitionTracingCallbacks = {
|
||||
onTransitionStart?: (transitionName: string, startTime: number) => void,
|
||||
onTransitionProgress?: (
|
||||
@@ -377,7 +388,9 @@ export type Dispatcher = {
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void,
|
||||
useEvent?: <T>(callback: () => T) => () => T,
|
||||
useEvent?: <Args, Return, F: (...Array<Args>) => Return>(
|
||||
callback: F,
|
||||
) => EventFunctionWrapper<Args, Return, F>,
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
@@ -117,6 +117,62 @@ describe('useEvent', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableUseEventHook
|
||||
it('can be defined more than once', () => {
|
||||
class IncrementButton extends React.PureComponent {
|
||||
increment = () => {
|
||||
this.props.onClick();
|
||||
};
|
||||
multiply = () => {
|
||||
this.props.onMouseEnter();
|
||||
};
|
||||
render() {
|
||||
return <Text text="Increment" />;
|
||||
}
|
||||
}
|
||||
|
||||
function Counter({incrementBy}) {
|
||||
const [count, updateCount] = useState(0);
|
||||
const onClick = useEvent(() => updateCount(c => c + incrementBy));
|
||||
const onMouseEnter = useEvent(() => {
|
||||
updateCount(c => c * incrementBy);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<IncrementButton
|
||||
onClick={() => onClick()}
|
||||
onMouseEnter={() => onMouseEnter()}
|
||||
ref={button}
|
||||
/>
|
||||
<Text text={'Count: ' + count} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const button = React.createRef(null);
|
||||
ReactNoop.render(<Counter incrementBy={5} />);
|
||||
expect(Scheduler).toFlushAndYield(['Increment', 'Count: 0']);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span('Increment'),
|
||||
span('Count: 0'),
|
||||
]);
|
||||
|
||||
act(button.current.increment);
|
||||
expect(Scheduler).toHaveYielded(['Increment', 'Count: 5']);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span('Increment'),
|
||||
span('Count: 5'),
|
||||
]);
|
||||
|
||||
act(button.current.multiply);
|
||||
expect(Scheduler).toHaveYielded(['Increment', 'Count: 25']);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span('Increment'),
|
||||
span('Count: 25'),
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableUseEventHook
|
||||
it('does not preserve `this` in event functions', () => {
|
||||
class GreetButton extends React.PureComponent {
|
||||
|
||||
+8
-2
@@ -7,7 +7,10 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {
|
||||
Dispatcher as DispatcherType,
|
||||
EventFunctionWrapper,
|
||||
} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import type {
|
||||
MutableSource,
|
||||
@@ -509,7 +512,10 @@ function throwOnUseEventCall() {
|
||||
);
|
||||
}
|
||||
|
||||
export function useEvent<T>(callback: () => T): () => T {
|
||||
export function useEvent<Args, Return, F: (...Array<Args>) => Return>(
|
||||
callback: F,
|
||||
): EventFunctionWrapper<Args, Return, F> {
|
||||
// $FlowIgnore[incompatible-return] useEvent doesn't work in Fizz
|
||||
return throwOnUseEventCall;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user