mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
Revert "Remove renderPhaseUpdates Map (#17484)"
This reverts commit 8a347ed024.
This commit is contained in:
parent
0b5a26a489
commit
6807fdea3d
|
|
@ -135,7 +135,7 @@ import {
|
|||
calculateChangedBits,
|
||||
scheduleWorkOnParentPath,
|
||||
} from './ReactFiberNewContext';
|
||||
import {renderWithHooks, bailoutHooks} from './ReactFiberHooks';
|
||||
import {resetHooks, renderWithHooks, bailoutHooks} from './ReactFiberHooks';
|
||||
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
|
||||
import {
|
||||
getMaskedContext,
|
||||
|
|
@ -1318,8 +1318,7 @@ function mountIndeterminateComponent(
|
|||
workInProgress.tag = ClassComponent;
|
||||
|
||||
// Throw out any hooks that were used.
|
||||
workInProgress.memoizedState = null;
|
||||
workInProgress.updateQueue = null;
|
||||
resetHooks();
|
||||
|
||||
// Push context providers early to prevent context stack mismatches.
|
||||
// During mounting we don't know the child context yet as the instance doesn't exist.
|
||||
|
|
|
|||
522
packages/react-reconciler/src/ReactFiberHooks.js
vendored
522
packages/react-reconciler/src/ReactFiberHooks.js
vendored
|
|
@ -192,7 +192,13 @@ let workInProgressHook: Hook | null = null;
|
|||
|
||||
// Whether an update was scheduled during the currently executing render pass.
|
||||
let didScheduleRenderPhaseUpdate: boolean = false;
|
||||
|
||||
// Lazily created map of render-phase updates
|
||||
let renderPhaseUpdates: Map<
|
||||
UpdateQueue<any, any>,
|
||||
Update<any, any>,
|
||||
> | null = null;
|
||||
// Counter to prevent infinite loops.
|
||||
let numberOfReRenders: number = 0;
|
||||
const RE_RENDER_LIMIT = 25;
|
||||
|
||||
// In DEV, this is the name of the currently executing primitive hook
|
||||
|
|
@ -386,6 +392,8 @@ export function renderWithHooks(
|
|||
// workInProgressHook = null;
|
||||
|
||||
// didScheduleRenderPhaseUpdate = false;
|
||||
// renderPhaseUpdates = null;
|
||||
// numberOfReRenders = 0;
|
||||
|
||||
// TODO Warn if no hooks are used at all during mount, then some are used during update.
|
||||
// Currently we will identify the update render as a mount because memoizedState === null.
|
||||
|
|
@ -417,17 +425,8 @@ export function renderWithHooks(
|
|||
let children = Component(props, refOrContext);
|
||||
|
||||
if (didScheduleRenderPhaseUpdate) {
|
||||
// Counter to prevent infinite loops.
|
||||
let numberOfReRenders: number = 0;
|
||||
do {
|
||||
didScheduleRenderPhaseUpdate = false;
|
||||
|
||||
invariant(
|
||||
numberOfReRenders < RE_RENDER_LIMIT,
|
||||
'Too many re-renders. React limits the number of renders to prevent ' +
|
||||
'an infinite loop.',
|
||||
);
|
||||
|
||||
numberOfReRenders += 1;
|
||||
if (__DEV__) {
|
||||
// Even when hot reloading, allow dependencies to stabilize
|
||||
|
|
@ -447,11 +446,14 @@ export function renderWithHooks(
|
|||
}
|
||||
|
||||
ReactCurrentDispatcher.current = __DEV__
|
||||
? HooksDispatcherOnRerenderInDEV
|
||||
: HooksDispatcherOnRerender;
|
||||
? HooksDispatcherOnUpdateInDEV
|
||||
: HooksDispatcherOnUpdate;
|
||||
|
||||
children = Component(props, refOrContext);
|
||||
} while (didScheduleRenderPhaseUpdate);
|
||||
|
||||
renderPhaseUpdates = null;
|
||||
numberOfReRenders = 0;
|
||||
}
|
||||
|
||||
// We can assume the previous dispatcher is always this one, since we set it
|
||||
|
|
@ -481,6 +483,8 @@ export function renderWithHooks(
|
|||
|
||||
// These were reset above
|
||||
// didScheduleRenderPhaseUpdate = false;
|
||||
// renderPhaseUpdates = null;
|
||||
// numberOfReRenders = 0;
|
||||
|
||||
invariant(
|
||||
!didRenderTooFewHooks,
|
||||
|
|
@ -503,31 +507,14 @@ export function bailoutHooks(
|
|||
}
|
||||
}
|
||||
|
||||
export function resetHooksAfterThrow(): void {
|
||||
export function resetHooks(): void {
|
||||
// We can assume the previous dispatcher is always this one, since we set it
|
||||
// at the beginning of the render phase and there's no re-entrancy.
|
||||
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
|
||||
|
||||
if (didScheduleRenderPhaseUpdate) {
|
||||
const current = (currentlyRenderingFiber: any).alternate;
|
||||
if (current !== null) {
|
||||
// There were render phase updates. These are only valid for this render
|
||||
// pass, which we are now aborting. Remove the updates from the queues so
|
||||
// they do not persist to the next render. We already did a single pass
|
||||
// through the whole list of hooks, so we know that any pending updates
|
||||
// must have been dispatched during the render phase. The ones that were
|
||||
// dispatched before we started rendering were already transferred to the
|
||||
// current hook's queue.
|
||||
let hook: Hook | null = current.memoizedState;
|
||||
while (hook !== null) {
|
||||
const queue = hook.queue;
|
||||
if (queue !== null) {
|
||||
queue.pending = null;
|
||||
}
|
||||
hook = hook.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This is used to reset the state of this module when a component throws.
|
||||
// It's also called inside mountIndeterminateComponent if we determine the
|
||||
// component is a module-style component.
|
||||
|
||||
renderExpirationTime = NoWork;
|
||||
currentlyRenderingFiber = (null: any);
|
||||
|
|
@ -543,6 +530,8 @@ export function resetHooksAfterThrow(): void {
|
|||
}
|
||||
|
||||
didScheduleRenderPhaseUpdate = false;
|
||||
renderPhaseUpdates = null;
|
||||
numberOfReRenders = 0;
|
||||
}
|
||||
|
||||
function mountWorkInProgressHook(): Hook {
|
||||
|
|
@ -678,6 +667,49 @@ function updateReducer<S, I, A>(
|
|||
|
||||
queue.lastRenderedReducer = reducer;
|
||||
|
||||
if (numberOfReRenders > 0) {
|
||||
// This is a re-render. Apply the new render phase updates to the previous
|
||||
// work-in-progress hook.
|
||||
const dispatch: Dispatch<A> = (queue.dispatch: any);
|
||||
if (renderPhaseUpdates !== null) {
|
||||
// Render phase updates are stored in a map of queue -> linked list
|
||||
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
|
||||
if (firstRenderPhaseUpdate !== undefined) {
|
||||
renderPhaseUpdates.delete(queue);
|
||||
let newState = hook.memoizedState;
|
||||
let update = firstRenderPhaseUpdate;
|
||||
do {
|
||||
// Process this render phase update. We don't have to check the
|
||||
// priority because it will always be the same as the current
|
||||
// render's.
|
||||
const action = update.action;
|
||||
newState = reducer(newState, action);
|
||||
update = update.next;
|
||||
} while (update !== null);
|
||||
|
||||
// Mark that the fiber performed work, but only if the new state is
|
||||
// different from the current state.
|
||||
if (!is(newState, hook.memoizedState)) {
|
||||
markWorkInProgressReceivedUpdate();
|
||||
}
|
||||
|
||||
hook.memoizedState = newState;
|
||||
// Don't persist the state accumulated from the render phase updates to
|
||||
// the base state unless the queue is empty.
|
||||
// TODO: Not sure if this is the desired semantics, but it's what we
|
||||
// do for gDSFP. I can't remember why.
|
||||
if (hook.baseQueue === null) {
|
||||
hook.baseState = newState;
|
||||
}
|
||||
|
||||
queue.lastRenderedState = newState;
|
||||
|
||||
return [newState, dispatch];
|
||||
}
|
||||
}
|
||||
return [hook.memoizedState, dispatch];
|
||||
}
|
||||
|
||||
const current: Hook = (currentHook: any);
|
||||
|
||||
// The last rebase update that is NOT part of the base state.
|
||||
|
|
@ -795,60 +827,6 @@ function updateReducer<S, I, A>(
|
|||
return [hook.memoizedState, dispatch];
|
||||
}
|
||||
|
||||
function rerenderReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const queue = hook.queue;
|
||||
invariant(
|
||||
queue !== null,
|
||||
'Should have a queue. This is likely a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
queue.lastRenderedReducer = reducer;
|
||||
|
||||
// This is a re-render. Apply the new render phase updates to the previous
|
||||
// work-in-progress hook.
|
||||
const dispatch: Dispatch<A> = (queue.dispatch: any);
|
||||
const lastRenderPhaseUpdate = queue.pending;
|
||||
let newState = hook.memoizedState;
|
||||
if (lastRenderPhaseUpdate !== null) {
|
||||
// The queue doesn't persist past this render pass.
|
||||
queue.pending = null;
|
||||
|
||||
const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
|
||||
let update = firstRenderPhaseUpdate;
|
||||
do {
|
||||
// Process this render phase update. We don't have to check the
|
||||
// priority because it will always be the same as the current
|
||||
// render's.
|
||||
const action = update.action;
|
||||
newState = reducer(newState, action);
|
||||
update = update.next;
|
||||
} while (update !== firstRenderPhaseUpdate);
|
||||
|
||||
// Mark that the fiber performed work, but only if the new state is
|
||||
// different from the current state.
|
||||
if (!is(newState, hook.memoizedState)) {
|
||||
markWorkInProgressReceivedUpdate();
|
||||
}
|
||||
|
||||
hook.memoizedState = newState;
|
||||
// Don't persist the state accumulated from the render phase updates to
|
||||
// the base state unless the queue is empty.
|
||||
// TODO: Not sure if this is the desired semantics, but it's what we
|
||||
// do for gDSFP. I can't remember why.
|
||||
if (hook.baseQueue === null) {
|
||||
hook.baseState = newState;
|
||||
}
|
||||
|
||||
queue.lastRenderedState = newState;
|
||||
}
|
||||
return [newState, dispatch];
|
||||
}
|
||||
|
||||
function mountState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
|
|
@ -879,12 +857,6 @@ function updateState<S>(
|
|||
return updateReducer(basicStateReducer, (initialState: any));
|
||||
}
|
||||
|
||||
function rerenderState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
return rerenderReducer(basicStateReducer, (initialState: any));
|
||||
}
|
||||
|
||||
function pushEffect(tag, create, destroy, deps) {
|
||||
const effect: Effect = {
|
||||
tag,
|
||||
|
|
@ -1254,6 +1226,12 @@ function dispatchAction<S, A>(
|
|||
queue: UpdateQueue<S, A>,
|
||||
action: A,
|
||||
) {
|
||||
invariant(
|
||||
numberOfReRenders < RE_RENDER_LIMIT,
|
||||
'Too many re-renders. React limits the number of renders to prevent ' +
|
||||
'an infinite loop.',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
if (typeof arguments[3] === 'function') {
|
||||
console.error(
|
||||
|
|
@ -1264,38 +1242,6 @@ function dispatchAction<S, A>(
|
|||
}
|
||||
}
|
||||
|
||||
const currentTime = requestCurrentTimeForUpdate();
|
||||
const suspenseConfig = requestCurrentSuspenseConfig();
|
||||
const expirationTime = computeExpirationForFiber(
|
||||
currentTime,
|
||||
fiber,
|
||||
suspenseConfig,
|
||||
);
|
||||
|
||||
const update: Update<S, A> = {
|
||||
expirationTime,
|
||||
suspenseConfig,
|
||||
action,
|
||||
eagerReducer: null,
|
||||
eagerState: null,
|
||||
next: (null: any),
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
update.priority = getCurrentPriorityLevel();
|
||||
}
|
||||
|
||||
// Append the update to the end of the list.
|
||||
const pending = queue.pending;
|
||||
if (pending === null) {
|
||||
// This is the first update. Create a circular list.
|
||||
update.next = update;
|
||||
} else {
|
||||
update.next = pending.next;
|
||||
pending.next = update;
|
||||
}
|
||||
queue.pending = update;
|
||||
|
||||
const alternate = fiber.alternate;
|
||||
if (
|
||||
fiber === currentlyRenderingFiber ||
|
||||
|
|
@ -1305,8 +1251,64 @@ function dispatchAction<S, A>(
|
|||
// queue -> linked list of updates. After this render pass, we'll restart
|
||||
// and apply the stashed updates on top of the work-in-progress hook.
|
||||
didScheduleRenderPhaseUpdate = true;
|
||||
update.expirationTime = renderExpirationTime;
|
||||
const update: Update<S, A> = {
|
||||
expirationTime: renderExpirationTime,
|
||||
suspenseConfig: null,
|
||||
action,
|
||||
eagerReducer: null,
|
||||
eagerState: null,
|
||||
next: (null: any),
|
||||
};
|
||||
if (__DEV__) {
|
||||
update.priority = getCurrentPriorityLevel();
|
||||
}
|
||||
if (renderPhaseUpdates === null) {
|
||||
renderPhaseUpdates = new Map();
|
||||
}
|
||||
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
|
||||
if (firstRenderPhaseUpdate === undefined) {
|
||||
renderPhaseUpdates.set(queue, update);
|
||||
} else {
|
||||
// Append the update to the end of the list.
|
||||
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
|
||||
while (lastRenderPhaseUpdate.next !== null) {
|
||||
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
|
||||
}
|
||||
lastRenderPhaseUpdate.next = update;
|
||||
}
|
||||
} else {
|
||||
const currentTime = requestCurrentTimeForUpdate();
|
||||
const suspenseConfig = requestCurrentSuspenseConfig();
|
||||
const expirationTime = computeExpirationForFiber(
|
||||
currentTime,
|
||||
fiber,
|
||||
suspenseConfig,
|
||||
);
|
||||
|
||||
const update: Update<S, A> = {
|
||||
expirationTime,
|
||||
suspenseConfig,
|
||||
action,
|
||||
eagerReducer: null,
|
||||
eagerState: null,
|
||||
next: (null: any),
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
update.priority = getCurrentPriorityLevel();
|
||||
}
|
||||
|
||||
// Append the update to the end of the list.
|
||||
const pending = queue.pending;
|
||||
if (pending === null) {
|
||||
// This is the first update. Create a circular list.
|
||||
update.next = update;
|
||||
} else {
|
||||
update.next = pending.next;
|
||||
pending.next = update;
|
||||
}
|
||||
queue.pending = update;
|
||||
|
||||
if (
|
||||
fiber.expirationTime === NoWork &&
|
||||
(alternate === null || alternate.expirationTime === NoWork)
|
||||
|
|
@ -1411,31 +1413,11 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
|||
useTransition: updateTransition,
|
||||
};
|
||||
|
||||
const HooksDispatcherOnRerender: Dispatcher = {
|
||||
readContext,
|
||||
|
||||
useCallback: updateCallback,
|
||||
useContext: readContext,
|
||||
useEffect: updateEffect,
|
||||
useImperativeHandle: updateImperativeHandle,
|
||||
useLayoutEffect: updateLayoutEffect,
|
||||
useMemo: updateMemo,
|
||||
useReducer: rerenderReducer,
|
||||
useRef: updateRef,
|
||||
useState: rerenderState,
|
||||
useDebugValue: updateDebugValue,
|
||||
useResponder: createResponderListener,
|
||||
useDeferredValue: updateDeferredValue,
|
||||
useTransition: updateTransition,
|
||||
};
|
||||
|
||||
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
|
||||
let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
|
||||
let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
|
||||
|
||||
if (__DEV__) {
|
||||
const warnInvalidContextAccess = () => {
|
||||
|
|
@ -1812,123 +1794,6 @@ if (__DEV__) {
|
|||
},
|
||||
};
|
||||
|
||||
HooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
return readContext(context, observedBits);
|
||||
},
|
||||
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
updateHookTypesDev();
|
||||
return updateCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
updateHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
updateHookTypesDev();
|
||||
return updateLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
|
||||
try {
|
||||
return updateMemo(create, deps);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
|
||||
try {
|
||||
return rerenderReducer(reducer, initialArg, init);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
updateHookTypesDev();
|
||||
return updateRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
|
||||
try {
|
||||
return rerenderState(initialState);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
updateHookTypesDev();
|
||||
return updateDebugValue(value, formatterFn);
|
||||
},
|
||||
useResponder<E, C>(
|
||||
responder: ReactEventResponder<E, C>,
|
||||
props,
|
||||
): ReactEventResponderListener<E, C> {
|
||||
currentHookNameInDev = 'useResponder';
|
||||
updateHookTypesDev();
|
||||
return createResponderListener(responder, props);
|
||||
},
|
||||
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
|
||||
currentHookNameInDev = 'useDeferredValue';
|
||||
updateHookTypesDev();
|
||||
return updateDeferredValue(value, config);
|
||||
},
|
||||
useTransition(
|
||||
config: SuspenseConfig | void | null,
|
||||
): [(() => void) => void, boolean] {
|
||||
currentHookNameInDev = 'useTransition';
|
||||
updateHookTypesDev();
|
||||
return updateTransition(config);
|
||||
},
|
||||
};
|
||||
|
||||
InvalidNestedHooksDispatcherOnMountInDEV = {
|
||||
readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
|
|
@ -2190,135 +2055,4 @@ if (__DEV__) {
|
|||
return updateTransition(config);
|
||||
},
|
||||
};
|
||||
|
||||
InvalidNestedHooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
warnInvalidContextAccess();
|
||||
return readContext(context, observedBits);
|
||||
},
|
||||
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
return updateMemo(create, deps);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
return rerenderReducer(reducer, initialArg, init);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
return rerenderState(initialState);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateDebugValue(value, formatterFn);
|
||||
},
|
||||
useResponder<E, C>(
|
||||
responder: ReactEventResponder<E, C>,
|
||||
props,
|
||||
): ReactEventResponderListener<E, C> {
|
||||
currentHookNameInDev = 'useResponder';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return createResponderListener(responder, props);
|
||||
},
|
||||
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
|
||||
currentHookNameInDev = 'useDeferredValue';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateDeferredValue(value, config);
|
||||
},
|
||||
useTransition(
|
||||
config: SuspenseConfig | void | null,
|
||||
): [(() => void) => void, boolean] {
|
||||
currentHookNameInDev = 'useTransition';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateTransition(config);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ import {
|
|||
} from './ReactFiberCommitWork';
|
||||
import {enqueueUpdate} from './ReactUpdateQueue';
|
||||
import {resetContextDependencies} from './ReactFiberNewContext';
|
||||
import {resetHooksAfterThrow, ContextOnlyDispatcher} from './ReactFiberHooks';
|
||||
import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
|
||||
import {createCapturedValue} from './ReactCapturedValue';
|
||||
|
||||
import {
|
||||
|
|
@ -1280,7 +1280,7 @@ function handleError(root, thrownValue) {
|
|||
try {
|
||||
// Reset module-level state that was set during the render phase.
|
||||
resetContextDependencies();
|
||||
resetHooksAfterThrow();
|
||||
resetHooks();
|
||||
resetCurrentDebugFiberInDEV();
|
||||
|
||||
if (workInProgress === null || workInProgress.return === null) {
|
||||
|
|
@ -2634,7 +2634,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
|||
// Keep this code in sync with handleError; any changes here must have
|
||||
// corresponding changes there.
|
||||
resetContextDependencies();
|
||||
resetHooksAfterThrow();
|
||||
resetHooks();
|
||||
// Don't reset current debug fiber, since we're about to work on the
|
||||
// same fiber again.
|
||||
|
||||
|
|
|
|||
|
|
@ -542,50 +542,6 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(22)]);
|
||||
});
|
||||
|
||||
it('discards render phase updates if something suspends', () => {
|
||||
const thenable = {then() {}};
|
||||
function Foo({signal}) {
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<Bar signal={signal} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function Bar({signal: newSignal}) {
|
||||
let [counter, setCounter] = useState(0);
|
||||
let [signal, setSignal] = useState(true);
|
||||
|
||||
// Increment a counter every time the signal changes
|
||||
if (signal !== newSignal) {
|
||||
setCounter(c => c + 1);
|
||||
setSignal(newSignal);
|
||||
if (counter === 0) {
|
||||
// We're suspending during a render that includes render phase
|
||||
// updates. Those updates should not persist to the next render.
|
||||
Scheduler.unstable_yieldValue('Suspend!');
|
||||
throw thenable;
|
||||
}
|
||||
}
|
||||
|
||||
return <Text text={counter} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render(<Foo signal={true} />);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([0]);
|
||||
expect(root).toMatchRenderedOutput(<span prop={0} />);
|
||||
|
||||
root.render(<Foo signal={false} />);
|
||||
expect(Scheduler).toFlushAndYield(['Suspend!']);
|
||||
expect(root).toMatchRenderedOutput(<span prop={0} />);
|
||||
|
||||
// Rendering again should suspend again.
|
||||
root.render(<Foo signal={false} />);
|
||||
expect(Scheduler).toFlushAndYield(['Suspend!']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useReducer', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user