mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[crud] Basic implementation
wip: - [ ] more tests - [ ] error handling - [ ] flow shenanigans
This commit is contained in:
+17
-5
@@ -14,6 +14,7 @@ import type {CapturedValue} from './ReactCapturedValue';
|
||||
|
||||
import {isRendering, setIsRendering} from './ReactCurrentFiber';
|
||||
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
|
||||
import {SimpleEffectKind} from './ReactFiberHooks';
|
||||
|
||||
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
|
||||
// TODO: Consider marking the whole bundle instead of these boundaries.
|
||||
@@ -177,11 +178,22 @@ export const callComponentWillUnmountInDEV: (
|
||||
|
||||
const callCreate = {
|
||||
'react-stack-bottom-frame': function (effect: Effect): (() => void) | void {
|
||||
const create = effect.create;
|
||||
const inst = effect.inst;
|
||||
const destroy = create();
|
||||
inst.destroy = destroy;
|
||||
return destroy;
|
||||
if (effect.kind === SimpleEffectKind) {
|
||||
const create = effect.create;
|
||||
const inst = effect.inst;
|
||||
const destroy = create();
|
||||
inst.destroy = destroy;
|
||||
return destroy;
|
||||
} else if (typeof effect.destroy === 'function') {
|
||||
const inst = effect.inst;
|
||||
const _destroy = effect.destroy;
|
||||
const destroy = () => {
|
||||
_destroy(effect.resource);
|
||||
effect.resource = null;
|
||||
};
|
||||
inst.destroy = destroy;
|
||||
return destroy;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+41
-5
@@ -71,6 +71,7 @@ import {
|
||||
} from './ReactFiberCallUserSpace';
|
||||
|
||||
import {runWithFiberInDEV} from './ReactCurrentFiber';
|
||||
import {ResourceEffectKind} from './ReactFiberHooks';
|
||||
|
||||
function shouldProfile(current: Fiber): boolean {
|
||||
return (
|
||||
@@ -151,15 +152,37 @@ export function commitHookEffectListMount(
|
||||
if ((flags & HookInsertion) !== NoHookEffect) {
|
||||
setIsRunningInsertionEffect(true);
|
||||
}
|
||||
destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
|
||||
if (effect.kind === ResourceEffectKind) {
|
||||
if (typeof effect.create === 'function') {
|
||||
effect.resource = effect.create();
|
||||
} else if (typeof effect.update === 'function') {
|
||||
// TODO: what about multiple updates?
|
||||
effect.update(effect.resource);
|
||||
}
|
||||
} else {
|
||||
destroy = runWithFiberInDEV(
|
||||
finishedWork,
|
||||
callCreateInDEV,
|
||||
effect,
|
||||
);
|
||||
}
|
||||
if ((flags & HookInsertion) !== NoHookEffect) {
|
||||
setIsRunningInsertionEffect(false);
|
||||
}
|
||||
} else {
|
||||
const create = effect.create;
|
||||
const inst = effect.inst;
|
||||
destroy = create();
|
||||
inst.destroy = destroy;
|
||||
if (effect.kind === ResourceEffectKind) {
|
||||
if (typeof effect.create === 'function') {
|
||||
effect.resource = effect.create();
|
||||
} else if (typeof effect.update === 'function') {
|
||||
// TODO: what about multiple updates?
|
||||
effect.update(effect.resource);
|
||||
}
|
||||
} else {
|
||||
const create = effect.create;
|
||||
const inst = effect.inst;
|
||||
destroy = create();
|
||||
inst.destroy = destroy;
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
@@ -243,6 +266,19 @@ export function commitHookEffectListUnmount(
|
||||
let effect = firstEffect;
|
||||
do {
|
||||
if ((effect.tag & flags) === flags) {
|
||||
if (effect.kind === ResourceEffectKind) {
|
||||
if (
|
||||
effect.resource != null &&
|
||||
typeof effect.destroy === 'function'
|
||||
) {
|
||||
effect.inst.destroy = function () {
|
||||
// $FlowFixMe trust me bro
|
||||
effect.destroy(effect.resource);
|
||||
effect.resource = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Unmount
|
||||
const inst = effect.inst;
|
||||
const destroy = inst.destroy;
|
||||
|
||||
+358
-14
@@ -48,6 +48,7 @@ import {
|
||||
disableLegacyMode,
|
||||
enableNoCloningMemoCache,
|
||||
enableContextProfiling,
|
||||
enableUseResourceEffectHook,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
REACT_CONTEXT_TYPE,
|
||||
@@ -218,12 +219,33 @@ type EffectInstance = {
|
||||
destroy: void | (() => void),
|
||||
};
|
||||
|
||||
export type Effect = {
|
||||
export const SimpleEffectKind: 0 = 0;
|
||||
export const ResourceEffectKind: 1 = 1;
|
||||
export type EffectKind = typeof SimpleEffectKind | typeof ResourceEffectKind;
|
||||
export type Effect = SimpleEffect | ResourceEffect;
|
||||
export type SimpleEffect = {
|
||||
kind: typeof SimpleEffectKind,
|
||||
tag: HookFlags,
|
||||
create: () => (() => void) | void,
|
||||
inst: EffectInstance,
|
||||
deps: Array<mixed> | null,
|
||||
create: () => (() => void) | void,
|
||||
createDeps: Array<mixed> | null,
|
||||
update: void | null,
|
||||
updateDeps: void | null,
|
||||
destroy: void | null,
|
||||
next: Effect,
|
||||
resource: mixed,
|
||||
};
|
||||
export type ResourceEffect = {
|
||||
kind: typeof ResourceEffectKind,
|
||||
tag: HookFlags,
|
||||
create: () => {},
|
||||
inst: EffectInstance,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: ((resource: mixed) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: ((resource: mixed) => void) | void,
|
||||
next: Effect,
|
||||
resource: mixed,
|
||||
};
|
||||
|
||||
type StoreInstance<T> = {
|
||||
@@ -1720,8 +1742,9 @@ function mountSyncExternalStore<T>(
|
||||
fiber.flags |= PassiveEffect;
|
||||
pushEffect(
|
||||
HookHasEffect | HookPassive,
|
||||
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
|
||||
createEffectInstance(),
|
||||
SimpleEffectKind,
|
||||
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
|
||||
null,
|
||||
);
|
||||
|
||||
@@ -1790,9 +1813,10 @@ function updateSyncExternalStore<T>(
|
||||
fiber.flags |= PassiveEffect;
|
||||
pushEffect(
|
||||
HookHasEffect | HookPassive,
|
||||
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
|
||||
createEffectInstance(),
|
||||
null,
|
||||
SimpleEffectKind,
|
||||
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Unless we're rendering a blocking lane, schedule a consistency check.
|
||||
@@ -2450,8 +2474,9 @@ function updateActionStateImpl<S, P>(
|
||||
currentlyRenderingFiber.flags |= PassiveEffect;
|
||||
pushEffect(
|
||||
HookHasEffect | HookPassive,
|
||||
actionStateActionEffect.bind(null, actionQueue, action),
|
||||
createEffectInstance(),
|
||||
SimpleEffectKind,
|
||||
actionStateActionEffect.bind(null, actionQueue, action),
|
||||
null,
|
||||
);
|
||||
}
|
||||
@@ -2510,15 +2535,26 @@ function rerenderActionState<S, P>(
|
||||
|
||||
function pushEffect(
|
||||
tag: HookFlags,
|
||||
create: () => (() => void) | void,
|
||||
inst: EffectInstance,
|
||||
deps: Array<mixed> | null,
|
||||
kind: EffectKind,
|
||||
create: () => (() => void) | {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (({}) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (({}) => void) | void,
|
||||
resource: mixed,
|
||||
): Effect {
|
||||
// $FlowFixMe jordan help pls
|
||||
const effect: Effect = {
|
||||
kind,
|
||||
tag,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
inst,
|
||||
deps,
|
||||
resource,
|
||||
// Circular
|
||||
next: (null: any),
|
||||
};
|
||||
@@ -2567,8 +2603,9 @@ function mountEffectImpl(
|
||||
currentlyRenderingFiber.flags |= fiberFlags;
|
||||
hook.memoizedState = pushEffect(
|
||||
HookHasEffect | hookFlags,
|
||||
create,
|
||||
createEffectInstance(),
|
||||
SimpleEffectKind,
|
||||
create,
|
||||
nextDeps,
|
||||
);
|
||||
}
|
||||
@@ -2589,9 +2626,16 @@ function updateEffectImpl(
|
||||
if (currentHook !== null) {
|
||||
if (nextDeps !== null) {
|
||||
const prevEffect: Effect = currentHook.memoizedState;
|
||||
const prevDeps = prevEffect.deps;
|
||||
const prevDeps = prevEffect.createDeps;
|
||||
// $FlowFixMe lauren
|
||||
if (areHookInputsEqual(nextDeps, prevDeps)) {
|
||||
hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);
|
||||
hook.memoizedState = pushEffect(
|
||||
hookFlags,
|
||||
inst,
|
||||
SimpleEffectKind,
|
||||
create,
|
||||
nextDeps,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2601,8 +2645,9 @@ function updateEffectImpl(
|
||||
|
||||
hook.memoizedState = pushEffect(
|
||||
HookHasEffect | hookFlags,
|
||||
create,
|
||||
inst,
|
||||
SimpleEffectKind,
|
||||
create,
|
||||
nextDeps,
|
||||
);
|
||||
}
|
||||
@@ -2639,6 +2684,148 @@ function updateEffect(
|
||||
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
}
|
||||
|
||||
function mountResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (({}) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
|
||||
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
|
||||
) {
|
||||
mountResourceEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
} else {
|
||||
mountResourceEffectImpl(
|
||||
PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mountResourceEffectImpl(
|
||||
fiberFlags: Flags,
|
||||
hookFlags: HookFlags,
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (({}) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
const hook = mountWorkInProgressHook();
|
||||
currentlyRenderingFiber.flags |= fiberFlags;
|
||||
hook.memoizedState = pushEffect(
|
||||
HookHasEffect | hookFlags,
|
||||
createEffectInstance(),
|
||||
ResourceEffectKind,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
|
||||
function updateResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (({}) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
updateResourceEffectImpl(
|
||||
PassiveEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
|
||||
function updateResourceEffectImpl(
|
||||
fiberFlags: Flags,
|
||||
hookFlags: HookFlags,
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (({}) => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const effect: Effect = hook.memoizedState;
|
||||
const inst = effect.inst;
|
||||
|
||||
const nextCreateDepsArray = createDeps != null ? createDeps : [];
|
||||
const nextUpdateDeps = updateDeps !== undefined ? updateDeps : null;
|
||||
let isCreateDepsSame: boolean;
|
||||
if (currentHook !== null) {
|
||||
const prevEffect: Effect = currentHook.memoizedState;
|
||||
const prevCreateDepsArray =
|
||||
prevEffect.createDeps != null ? prevEffect.createDeps : [];
|
||||
isCreateDepsSame = areHookInputsEqual(
|
||||
nextCreateDepsArray,
|
||||
prevCreateDepsArray,
|
||||
);
|
||||
|
||||
if (nextUpdateDeps !== null) {
|
||||
const prevUpdateDeps =
|
||||
prevEffect.updateDeps != null ? prevEffect.updateDeps : null;
|
||||
if (
|
||||
isCreateDepsSame &&
|
||||
areHookInputsEqual(nextUpdateDeps, prevUpdateDeps)
|
||||
) {
|
||||
hook.memoizedState = pushEffect(
|
||||
hookFlags,
|
||||
inst,
|
||||
ResourceEffectKind,
|
||||
create,
|
||||
nextUpdateDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
hook.memoizedState.resource = prevEffect.resource;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentlyRenderingFiber.flags |= fiberFlags;
|
||||
|
||||
hook.memoizedState = pushEffect(
|
||||
HookHasEffect | hookFlags,
|
||||
inst,
|
||||
ResourceEffectKind,
|
||||
// $FlowFixMe lauren
|
||||
isCreateDepsSame ? undefined : create,
|
||||
nextCreateDepsArray,
|
||||
update,
|
||||
nextUpdateDeps,
|
||||
isCreateDepsSame ? undefined : destroy,
|
||||
);
|
||||
if (currentHook != null) {
|
||||
const currentHookState: Effect = currentHook.memoizedState;
|
||||
hook.memoizedState.resource = currentHookState.resource;
|
||||
}
|
||||
}
|
||||
|
||||
function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>(
|
||||
payload: EventFunctionPayload<Args, Return, F>,
|
||||
) {
|
||||
@@ -3789,6 +3976,9 @@ if (enableUseMemoCacheHook) {
|
||||
if (enableUseEffectEventHook) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError;
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useHostTransitionStatus =
|
||||
throwInvalidHookError;
|
||||
@@ -3832,6 +4022,9 @@ if (enableUseMemoCacheHook) {
|
||||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect;
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -3875,6 +4068,10 @@ if (enableUseMemoCacheHook) {
|
||||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useResourceEffect =
|
||||
updateResourceEffect;
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -3918,6 +4115,10 @@ if (enableUseMemoCacheHook) {
|
||||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent;
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useResourceEffect =
|
||||
updateResourceEffect;
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -4108,6 +4309,26 @@ if (__DEV__) {
|
||||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
mountHookTypesDev();
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -4300,6 +4521,26 @@ if (__DEV__) {
|
||||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
updateHookTypesDev();
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -4491,6 +4732,26 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -4682,6 +4943,26 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -4897,6 +5178,27 @@ if (__DEV__) {
|
||||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (InvalidNestedHooksDispatcherOnMountInDEV) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
): void {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -5115,6 +5417,27 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
@@ -5333,6 +5656,27 @@ if (__DEV__) {
|
||||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableUseResourceEffectHook) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
|
||||
function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) {
|
||||
currentHookNameInDev = 'useResourceEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
};
|
||||
}
|
||||
if (enableAsyncActions) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
|
||||
useHostTransitionStatus;
|
||||
|
||||
@@ -47,6 +47,7 @@ export type HookType =
|
||||
| 'useRef'
|
||||
| 'useEffect'
|
||||
| 'useEffectEvent'
|
||||
| 'useResourceEffect'
|
||||
| 'useInsertionEffect'
|
||||
| 'useLayoutEffect'
|
||||
| 'useCallback'
|
||||
@@ -412,6 +413,13 @@ export type Dispatcher = {
|
||||
deps: Array<mixed> | void | null,
|
||||
): void,
|
||||
useEffectEvent?: <Args, F: (...Array<Args>) => mixed>(callback: F) => F,
|
||||
useResourceEffect?: (
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
) => void,
|
||||
useInsertionEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
|
||||
@@ -41,6 +41,7 @@ let waitFor;
|
||||
let waitForThrow;
|
||||
let waitForPaint;
|
||||
let assertLog;
|
||||
let useResourceEffect;
|
||||
|
||||
describe('ReactHooksWithNoopRenderer', () => {
|
||||
beforeEach(() => {
|
||||
@@ -66,6 +67,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
useDeferredValue = React.useDeferredValue;
|
||||
Suspense = React.Suspense;
|
||||
Activity = React.unstable_Activity;
|
||||
useResourceEffect = React.experimental_useResourceEffect;
|
||||
ContinuousEventPriority =
|
||||
require('react-reconciler/constants').ContinuousEventPriority;
|
||||
if (gate(flags => flags.enableSuspenseList)) {
|
||||
@@ -3252,6 +3254,171 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
describe('useResourceEffect', () => {
|
||||
class Resource {
|
||||
id: string;
|
||||
opts: mixed;
|
||||
constructor(id, opts) {
|
||||
this.id = id;
|
||||
this.opts = opts;
|
||||
Scheduler.log(`create(${this.id}, ${this.opts.username})`);
|
||||
}
|
||||
update(opts) {
|
||||
this.opts = opts;
|
||||
Scheduler.log(`update(${this.id}, ${this.opts.username})`);
|
||||
}
|
||||
destroy() {
|
||||
Scheduler.log(`destroy(${this.id}, ${this.opts.username})`);
|
||||
}
|
||||
}
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
it('simple mount and update', async () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
() => new Resource(id, opts),
|
||||
[id],
|
||||
resource => resource.update(opts),
|
||||
[opts],
|
||||
resource => resource.destroy(),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Jack" />);
|
||||
});
|
||||
assertLog(['create(1, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Lauren" />);
|
||||
});
|
||||
assertLog(['update(1, Lauren)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Jordan" />);
|
||||
});
|
||||
assertLog(['update(1, Jordan)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={2} username="Jack" />);
|
||||
});
|
||||
assertLog(['destroy(1, Jordan)', 'create(2, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(null);
|
||||
});
|
||||
assertLog(['destroy(2, Jack)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
it('simple mount with no update', async () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
() => new Resource(id, opts),
|
||||
[id],
|
||||
resource => resource.update(opts),
|
||||
[opts],
|
||||
resource => resource.destroy(),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Jack" />);
|
||||
});
|
||||
assertLog(['create(1, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(null);
|
||||
});
|
||||
assertLog(['destroy(1, Jack)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
it('calls update on every render if no deps are specified', async () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
() => new Resource(id, opts),
|
||||
[id],
|
||||
resource => resource.update(opts),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Jack" />);
|
||||
});
|
||||
assertLog(['create(1, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Jack" />);
|
||||
});
|
||||
assertLog(['update(1, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={2} username="Jack" />);
|
||||
});
|
||||
assertLog(['create(2, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={2} username="Lauren" />);
|
||||
});
|
||||
|
||||
assertLog(['update(2, Lauren)']);
|
||||
});
|
||||
|
||||
// @gate enableUseResourceEffectHook
|
||||
it('calls useResourceEffect methods only on mount/unmount', async () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
function App({id, username}) {
|
||||
const opts = useMemo(() => {
|
||||
return {username};
|
||||
}, [username]);
|
||||
useResourceEffect(
|
||||
() => new Resource(id, opts),
|
||||
[],
|
||||
resource => resource.update(opts),
|
||||
[],
|
||||
resource => resource.destroy(),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={1} username="Jack" />);
|
||||
});
|
||||
assertLog(['create(1, Jack)']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App id={2} username="Joe" />);
|
||||
});
|
||||
assertLog([]);
|
||||
|
||||
await act(() => {
|
||||
root.render(null);
|
||||
});
|
||||
assertLog(['destroy(1, Jack)']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCallback', () => {
|
||||
it('memoizes callback by comparing inputs', async () => {
|
||||
class IncrementButton extends React.PureComponent {
|
||||
|
||||
@@ -60,6 +60,7 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
|
||||
@@ -41,6 +41,7 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
|
||||
@@ -41,6 +41,7 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
createElement,
|
||||
createRef,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
forwardRef,
|
||||
Fragment,
|
||||
isValidElement,
|
||||
|
||||
@@ -61,6 +61,7 @@ export {
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
experimental_useEffectEvent,
|
||||
experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useInsertionEffect,
|
||||
useLayoutEffect,
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
useContext,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useInsertionEffect,
|
||||
@@ -89,6 +90,7 @@ export {
|
||||
useContext,
|
||||
useEffect,
|
||||
useEffectEvent as experimental_useEffectEvent,
|
||||
useResourceEffect as experimental_useResourceEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useInsertionEffect,
|
||||
|
||||
@@ -226,6 +226,24 @@ export function useEffectEvent<Args, F: (...Array<Args>) => mixed>(
|
||||
return dispatcher.useEffectEvent(callback);
|
||||
}
|
||||
|
||||
export function useResourceEffect(
|
||||
create: () => {} | void,
|
||||
createDeps: Array<mixed> | void | null,
|
||||
update: (() => void) | void,
|
||||
updateDeps: Array<mixed> | void | null,
|
||||
destroy: (() => void) | void,
|
||||
): void {
|
||||
const dispatcher = resolveDispatcher();
|
||||
// $FlowFixMe[not-a-function] This is unstable, thus optional
|
||||
return dispatcher.useResourceEffect(
|
||||
create,
|
||||
createDeps,
|
||||
update,
|
||||
updateDeps,
|
||||
destroy,
|
||||
);
|
||||
}
|
||||
|
||||
export function useOptimistic<S, A>(
|
||||
passthrough: S,
|
||||
reducer: ?(S, A) => S,
|
||||
|
||||
@@ -161,6 +161,11 @@ export const transitionLaneExpirationMs = 5000;
|
||||
*/
|
||||
export const enableInfiniteRenderLoopDetection = false;
|
||||
|
||||
/**
|
||||
* Experimental new hook for better managing resources in effects.
|
||||
*/
|
||||
export const enableUseResourceEffectHook = __EXPERIMENTAL__;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Ready for next major.
|
||||
//
|
||||
|
||||
@@ -94,6 +94,7 @@ export const retryLaneExpirationMs = 5000;
|
||||
export const syncLaneExpirationMs = 250;
|
||||
export const transitionLaneExpirationMs = 5000;
|
||||
export const useModernStrictMode = true;
|
||||
export const enableUseResourceEffectHook = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -86,6 +86,7 @@ export const syncLaneExpirationMs = 250;
|
||||
export const transitionLaneExpirationMs = 5000;
|
||||
export const useModernStrictMode = true;
|
||||
export const enableSiblingPrerendering = false;
|
||||
export const enableUseResourceEffectHook = false;
|
||||
|
||||
// Profiling Only
|
||||
export const enableProfilerTimer = __PROFILE__;
|
||||
|
||||
@@ -84,6 +84,8 @@ export const renameElementSymbol = true;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const enableSiblingPrerendering = false;
|
||||
|
||||
export const enableUseResourceEffectHook = false;
|
||||
|
||||
// TODO: This must be in sync with the main ReactFeatureFlags file because
|
||||
// the Test Renderer's value must be the same as the one used by the
|
||||
// react package.
|
||||
|
||||
@@ -81,6 +81,7 @@ export const transitionLaneExpirationMs = 5000;
|
||||
export const useModernStrictMode = true;
|
||||
export const enableFabricCompleteRootInCommitPhase = false;
|
||||
export const enableSiblingPrerendering = false;
|
||||
export const enableUseResourceEffectHook = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -97,5 +97,7 @@ export const enableOwnerStacks = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const enableSiblingPrerendering = false;
|
||||
|
||||
export const enableUseResourceEffectHook = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -125,5 +125,7 @@ export const disableLegacyMode: boolean =
|
||||
export const enableOwnerStacks = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
|
||||
export const enableUseResourceEffectHook = true;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
Reference in New Issue
Block a user