React Fiber Hooks 实现
负责处理 Hooks 核心的关键文件,实现了函数组件的状态管理和生命周期机制。
信息
5261 行的大文件。。。。,仅次于 'ReactFiberCommitWork' 的 5283 行和 'ReactFiberWorkLoop' 的 5338 行
本文档有效(嗯,其实都挺有效的)代码数到 3982 行,而 3984 - 5260 行是测试环境配置
备注
乐观更新总是同步进行的
一、核心概念
- Fiber 节点储存 Hooks : 每个函数组件对应的 Fiber 节点通过 memoizedSate 属性储存一个 Hooks 链表
- Hook 累心表示 : 每个 Hook 通过 memoizedState 上的隐式标记区分类型( 如
useState、useEffect)
1. 关键函数解析
- Hook 调度入口 :
renderWithHooks函数组件的渲染入口,负责:- 设置当前 Fiber 和渲染阶段
- 执行组件函数并收集 Hooks
- 处理副作用( 如
layoutEffect)
- Hook 管理 :
mountWorkInProgressHook/updateWorkInProgressWork创建或复用当前渲染的 Hook 节点,维护 Hooks 链表顺序 - 具体 Hook 实现:
useState/useReducer- 挂载阶段 : 创建 Hook , 初始化 state 和更新列队
- 更新阶段 : 处理
dispatch,调度更新
useEffect/useLayoutEffect- 将
effect对象(包含回调函数和依赖)添加到 Fiber 的 effects 链表 - 在渲染提交阶段执行副作用
- 将
useContext: 从 Context 中获取最新值并触发重新渲染useRef: 创建可跨渲染周期保存值的对象(current属性 )
2. 更新机制
- 更新列队处理 :
- 如
setState会生成更新对象,加入 Hook 的queue.pending链表 - 通过
processUpdateQueue计算最新的state
- 如
- 副作用调度 :
useEffect的回调会被包装成 Effect 对象,挂载在 Fiber 的 effects 链表- 在
commit阶段执行:commitWork➞commitHookEffectList()
3. 设计亮点
- 链表结构 : Hooks 链表保证执行顺序与声明一致,支持动态增减 Hooks
- 双缓存机制 : 使用
current和workInProgress两颗 Fiber 树,避免重复计算 - 调度集成 : Hooks 更新无缝接入 React 的 Fiber 调度和批处理系统
二、使用钩子渲染
备注
callComponentInDEV()由 ReactFiberCallUserSpace#callComponentInDEV 实现setIsStrictModeForDevtools()由 ReactFiberDevToolsHook#setIsStrictModeForDevtools 实现
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
if (__DEV__) {
hookTypesDev =
current !== null
? (current._debugHookTypes as any as Array<HookType>)
: null;
hookTypesUpdateIndexDev = -1;
// Used for hot reloading:
// 用于热重载:
ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
warnIfAsyncClientComponent(Component);
}
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// The following should have already been reset
// 以下内容应已被重置
// currentHook = null;
// workInProgressHook = null;
// didScheduleRenderPhaseUpdate = false;
// localIdCounter = 0;
// thenableIndexCounter = 0;
// thenableState = null;
// 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.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
//
// TODO 如果在挂载期间完全没有使用钩子,然后在更新期间使用了一些钩子,则发出警告。
// 目前我们会将更新渲染识别为挂载,因为 memoizedState === null。
// 这很棘手,因为对于某些类型的组件(例如 React.lazy)这是有效的
// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so memoizedState would be null during updates and mounts.
//
// 使用 memoizedState 来区分挂载/更新 只有在至少使用一个有状态的 Hook 时才有效。
// 无状态的 Hook(例如 context)不会被添加到 memoizedState,
// 因此 memoizedState 在更新和挂载期间会为 null。
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
// This dispatcher handles an edge case where a component is updating,
// but no stateful hooks have been used.
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
// This dispatcher does that.
//
// 这个调度器处理一个边缘情况,即组件正在更新,
// 但没有使用任何有状态的 hooks。
// 我们希望匹配生产代码的行为(会使用 HooksDispatcherOnMount),
// 但增加额外的开发环境验证,以确保 hooks 的顺序没有改变。
// 这个调度器就是这样做的。
ReactSharedInternals.H = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
}
} else {
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
// In Strict Mode, during development, user functions are double invoked to
// help detect side effects. The logic for how this is implemented for in
// hook components is a bit complex so let's break it down.
//
// 在严格模式下,在开发过程中,用户函数会被调用两次,以帮助检测副作用。在钩子组件中实现
// 这一点的逻辑有点复杂,所以让我们拆解一下。
//
// We will invoke the entire component function twice. However, during the
// second invocation of the component, the hook state from the first
// invocation will be reused. That means things like `useMemo` functions won't
// run again, because the deps will match and the memoized result will
// be reused.
//
// 我们将调用整个组件函数两次。然而,在组件的第二次调用中,第一次调用的钩子状态将被重用。这
// 意味着像 `useMemo` 这样的函数不会再次运行,因为依赖项会匹配,并且会重用之前记忆的结果。
//
// We want memoized functions to run twice, too, so account for this, user
// functions are double invoked during the *first* invocation of the component
// function, and are *not* double invoked during the second incovation:
//
// 我们希望记忆化函数也能运行两次,所以请考虑这一点,用户
// 在组件函数的*第一次*调用期间,函数会被调用两次
// 在第二次调用期间,函数*不会*被调用两次:
//
// - First execution of component function: user functions are double invoked
// - Second execution of component function (in Strict Mode, during
// development): user functions are not double invoked.
//
// - 组件函数的第一次执行:用户函数会被调用两次
// - 组件函数的第二次执行(在严格模式下,在开发过程中):用户函数不会被调用两次。
//
// This is intentional for a few reasons; most importantly, it's because of
// how `use` works when something suspends: it reuses the promise that was
// passed during the first attempt. This is itself a form of memoization.
// We need to be able to memoize the reactive inputs to the `use` call using
// a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must
// come from the same component invocation as the output.
//
// 这是有意为之的,原因有几个;最重要的是,因为当某些东西挂起时,`use` 的工作方式:
// 它会重用在第一次尝试时传入的 promise。这本身就是一种记忆化(memoization)。
// 我们需要能够使用一个 hook(即 `useMemo`)对 `use` 调用的响应式输入进行记忆化,这
// 意味着,`use` 的响应式输入必须来自与输出相同的组件调用。
//
// There are plenty of tests to ensure this behavior is correct.
// 有很多测试可以确保这种行为是正确的。
const shouldDoubleRenderDEV =
__DEV__ && (workInProgress.mode & StrictLegacyMode) !== NoMode;
shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;
let children = __DEV__
? callComponentInDEV(Component, props, secondArg)
: Component(props, secondArg);
shouldDoubleInvokeUserFnsInHooksDEV = false;
// Check if there was a render phase update
// 检查是否有渲染阶段的更新
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// Keep rendering until the component stabilizes (there are no more render
// phase updates).
//
// 继续渲染直到组件稳定(没有更多渲染阶段的更新)。
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
}
if (shouldDoubleRenderDEV) {
// In development, components are invoked twice to help detect side effects.
// 在开发过程中,组件会被调用两次以帮助检测副作用。
setIsStrictModeForDevtools(true);
try {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
} finally {
setIsStrictModeForDevtools(false);
}
}
finishRenderingHooks(current, workInProgress, Component);
return children;
}
三、使用钩子重放挂起组件
export function replaySuspendedComponentWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
): any {
// This function is used to replay a component that previously suspended,
// after its data resolves.
//
// 这个函数用于在组件之前挂起后,在其数据解析后重新播放组件。
//
// It's a simplified version of renderWithHooks, but it doesn't need to do
// most of the set up work because they weren't reset when we suspended; they
// only get reset when the component either completes (finishRenderingHooks)
// or unwinds (resetHooksOnUnwind).
//
// 这是 renderWithHooks 的简化版本,但它不需要执行大部分设置工作,因为当我们挂起时这些工作
// 没有被重置;它们只有在组件完成(finishRenderingHooks)或
// 回退(resetHooksOnUnwind)时才会被重置。
if (__DEV__) {
hookTypesUpdateIndexDev = -1;
// Used for hot reloading:
// 用于热重载:
ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
}
// renderWithHooks only resets the updateQueue but does not clear it, since
// it needs to work for both this case (suspense replay) as well as for double
// renders in dev and setState-in-render. However, for the suspense replay case
// we need to reset the updateQueue to correctly handle unmount effects, so we
// clear the queue here
//
// renderWithHooks 只会重置 updateQueue,但不会清空它,因为
// 它需要同时适用于这种情况(suspense 重播)以及开发环境下的双重渲染和渲染时的 setState。
// 然而,对于 suspense 重播的情况
// 我们需要重置 updateQueue 以正确处理卸载效果,所以我们在这里清空队列
workInProgress.updateQueue = null;
const children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
finishRenderingHooks(current, workInProgress, Component);
return children;
}
四、使用钩子呈现过渡感知的宿主组件
export function renderTransitionAwareHostComponentWithHooks(
current: Fiber | null,
workInProgress: Fiber,
lanes: Lanes,
): TransitionStatus {
return renderWithHooks(
current,
workInProgress,
TransitionAwareHostComponent,
null,
null,
lanes,
);
}
五、过渡感知宿主环境组件
export function TransitionAwareHostComponent(): TransitionStatus {
const dispatcher: any = ReactSharedInternals.H;
const [maybeThenable] = dispatcher.useState();
let nextState;
if (typeof maybeThenable.then === 'function') {
const thenable: Thenable<TransitionStatus> = (maybeThenable: any);
nextState = useThenable(thenable);
} else {
const status: TransitionStatus = maybeThenable;
nextState = status;
}
// The "reset state" is an object. If it changes, that means something
// requested that we reset the form.
//
// “重置状态”是一个对象。如果它发生变化,意味着有某些操作请求我们重置表单。
const [nextResetState] = dispatcher.useState();
const prevResetState =
currentHook !== null ? currentHook.memoizedState : null;
if (prevResetState !== nextResetState) {
// Schedule a form reset
// 计划表单重置
currentlyRenderingFiber.flags |= FormReset;
}
return nextState;
}
六、检查是否渲染了ID钩子
export function checkDidRenderIdHook(): boolean {
// This should be called immediately after every renderWithHooks call.
// Conceptually, it's part of the return value of renderWithHooks; it's only a
// separate function to avoid using an array tuple.
//
// 应该在每次 renderWithHooks 调用后立即调用。
// 从概念上讲,它是 renderWithHooks 返回值的一部分;它只是一个独立的函数,以避免使用数组元组。
const didRenderIdHook = localIdCounter !== 0;
localIdCounter = 0;
return didRenderIdHook;
}
七、救助挂钩
备注
removeLanes()由 ReactFiberLane#removeLanes 实现
export function bailoutHooks(
current: Fiber,
workInProgress: Fiber,
lanes: Lanes,
): void {
workInProgress.updateQueue = current.updateQueue;
// TODO: Don't need to reset the flags here, because they're reset in the
// complete phase (bubbleProperties).
//
// TODO: 这里不需要重置标志,因为它们会在完成阶段(bubbleProperties)被重置。
if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
workInProgress.flags &= ~(
MountPassiveDevEffect |
MountLayoutDevEffect |
PassiveEffect |
UpdateEffect
);
} else {
workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
}
current.lanes = removeLanes(current.lanes, lanes);
}
八、抛出后重置钩子
export function resetHooksAfterThrow(): void {
// This is called immediaetly after a throw. It shouldn't reset the entire
// module state, because the work loop might decide to replay the component
// again without rewinding.
//
// 这是在抛出异常后立即调用的。它不应该重置整个
// 模块状态,因为工作循环可能决定再次重放组件
// 而无需回滚。
//
// It should only reset things like the current dispatcher, to prevent hooks
// from being called outside of a component.
//
// 它应该只重置当前的调度器等内容,以防止在组件外调用钩子。
currentlyRenderingFiber = null as any;
// 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-entrance.
//
// 我们可以假设前一个调度器总是这个,因为我们在渲染阶段开始时设置了它,并且没有再次进入。
ReactSharedInternals.H = ContextOnlyDispatcher;
}
九、在回退时重置钩子
export function resetHooksOnUnwind(workInProgress: Fiber): void {
if (didScheduleRenderPhaseUpdate) {
// There were render phase updates. These are only valid for this render
// phase, which we are now aborting. Remove the updates from the queues so
// they do not persist to the next render. Do not remove updates from hooks
// that weren't processed.
//
// 有渲染阶段的更新。这些更新仅对当前渲染阶段有效,而我们现在正在中止它。将这些
// 更新从队列中移除,以免它们延续到下一次渲染。不要移除未处理的 hook 中的更新。
//
// Only reset the updates from the queue if it has a clone. If it does
// not have a clone, that means it wasn't processed, and the updates were
// scheduled before we entered the render phase.
//
// 只有在队列中有克隆时才重置更新。如果没有克隆,意味着它尚未处理,并且更新
// 是在我们进入渲染阶段之前安排的。
let hook: Hook | null = workInProgress.memoizedState;
while (hook !== null) {
const queue = hook.queue;
if (queue !== null) {
queue.pending = null;
}
hook = hook.next;
}
didScheduleRenderPhaseUpdate = false;
}
renderLanes = NoLanes;
currentlyRenderingFiber = null as any;
currentHook = null;
workInProgressHook = null;
if (__DEV__) {
hookTypesDev = null;
hookTypesUpdateIndexDev = -1;
currentHookNameInDev = null;
}
didScheduleRenderPhaseUpdateDuringThisPass = false;
localIdCounter = 0;
thenableIndexCounter = 0;
thenableState = null;
}
十、开始宿主环境过渡
备注
startHostActionTimer()由 ReactProfilerTimer#startHostActionTimer 实现
export function startHostTransition<F>(
formFiber: Fiber,
pendingState: TransitionStatus,
action: ((F) => mixed) | null,
formData: F,
): void {
if (formFiber.tag !== HostComponent) {
throw new Error(
'Expected the form instance to be a HostComponent. This ' +
'is a bug in React.',
);
}
const stateHook = ensureFormComponentIsStateful(formFiber);
const queue: UpdateQueue<
Thenable<TransitionStatus> | TransitionStatus,
BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>
> = stateHook.queue;
startHostActionTimer(formFiber);
startTransition(
formFiber,
queue,
pendingState,
NoPendingHostTransition,
// TODO: `startTransition` both sets the pending state and dispatches
// the action, if one is provided. Consider refactoring these two
// concerns to avoid the extra lambda.
//
// 待办事项:`startTransition` 同时设置挂起状态并分发
// 动作(如果提供了的话)。考虑重构这两个
// 关注点,以避免额外的 lambda。
action === null
? // No action was provided, but we still call `startTransition` to
// set the pending form status.
//
// 没有提供操作,但我们仍然调用 `startTransition` 来设置待处理表单状态。
noop
: () => {
// Automatically reset the form when the action completes.
// 当操作完成时自动重置表单。
requestFormReset(formFiber);
return action(formData);
},
);
}
十一、请求表单重置
备注
requestCurrentTransition()由 ReactFiberTransition#requestCurrentTransition 实现requestUpdateLane()由 ReactFiberWorkLoop 提供
export function requestFormReset(formFiber: Fiber) {
const transition = requestCurrentTransition();
if (transition === null) {
if (__DEV__) {
// An optimistic update occurred, but startTransition is not on the stack.
// The form reset will be scheduled at default (sync) priority, which
// is probably not what the user intended. Most likely because the
// requestFormReset call happened after an `await`.
// TODO: Theoretically, requestFormReset is still useful even for
// non-transition updates because it allows you to update defaultValue
// synchronously and then wait to reset until after the update commits.
// I've chosen to warn anyway because it's more likely the `await` mistake
// described above. But arguably we shouldn't.
//
// 发生了乐观更新,但 startTransition 不在堆栈上。
// 表单重置将以默认(同步)优先级调度,这可能不是用户的本意。
// 很可能是因为 requestFormReset 调用发生在 `await` 之后。
// TODO: 从理论上讲,即使是非过渡更新,requestFormReset 仍然有用,
// 因为它允许你同步更新 defaultValue,然后等待在更新提交后重置。
// 我选择仍然发出警告,因为更可能是上面描述的 `await` 错误。
// 但可以说我们不应该这样做。
console.error(
'requestFormReset was called outside a transition or action. To ' +
'fix, move to an action, or wrap with startTransition.',
);
}
} else if (enableGestureTransition && transition.gesture) {
throw new Error(
'Cannot requestFormReset() inside a startGestureTransition. ' +
'There should be no side-effects associated with starting a ' +
'Gesture until its Action is invoked. Move side-effects to the ' +
'Action instead.',
);
}
let stateHook: Hook = ensureFormComponentIsStateful(formFiber);
const newResetState = {};
if (stateHook.next === null) {
// Hack alert. If formFiber is the workInProgress Fiber then
// we might get a broken intermediate state. Try the alternate
// instead.
// TODO: We should really stash the Queue somewhere stateful
// just like how setState binds the Queue.
//
// 侵入警告。如果 formFiber 是 workInProgress Fiber,那么
// 我们可能会得到一个中间破损状态。尝试使用备用方案。
// TODO: 我们实际上应该把 Queue 存在某个有状态的地方,
// 就像 setState 绑定 Queue 一样。
stateHook = (formFiber.alternate as any).memoizedState;
}
const resetStateHook: Hook = stateHook.next as any;
const resetStateQueue = resetStateHook.queue;
dispatchSetStateInternal(
formFiber,
resetStateQueue,
newResetState,
requestUpdateLane(formFiber),
);
}
十二、导出的常量
1. 仅上下文分发器
备注
readContext()由 ReactFiberNewContext#readContext 实现
其余方法均是本文档实现
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
use,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useInsertionEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
useHostTransitionStatus: throwInvalidHookError,
useFormState: throwInvalidHookError,
useActionState: throwInvalidHookError,
useOptimistic: throwInvalidHookError,
useMemoCache: throwInvalidHookError,
useCacheRefresh: throwInvalidHookError,
};
if (enableUseEffectEventHook) {
(ContextOnlyDispatcher as Dispatcher).useEffectEvent = throwInvalidHookError;
}
十三、常量
1. 更新调试值
const updateDebugValue = mountDebugValue;
2. 无操作
信息
对,没错,这是个函数。
const noop = () => {};
3. 在挂载时挂钩调度器
备注
readContext()由 ReactFiberNewContext#readContext 实现
const HooksDispatcherOnMount: Dispatcher = {
readContext,
use,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
useHostTransitionStatus: useHostTransitionStatus,
useFormState: mountActionState,
useActionState: mountActionState,
useOptimistic: mountOptimistic,
useMemoCache,
useCacheRefresh: mountRefresh,
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnMount as Dispatcher).useEffectEvent = mountEvent;
}
4. 在更新四挂钩调度器
备注
readContext()由 ReactFiberNewContext#readContext 实现
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
useHostTransitionStatus: useHostTransitionStatus,
useFormState: updateActionState,
useActionState: updateActionState,
useOptimistic: updateOptimistic,
useMemoCache,
useCacheRefresh: updateRefresh,
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnUpdate as Dispatcher).useEffectEvent = updateEvent;
}
5. 在重新渲染时挂钩调度器
备注
readContext()由 ReactFiberNewContext#readContext 实现
const HooksDispatcherOnRerender: Dispatcher = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: rerenderReducer,
useRef: updateRef,
useState: rerenderState,
useDebugValue: updateDebugValue,
useDeferredValue: rerenderDeferredValue,
useTransition: rerenderTransition,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
useHostTransitionStatus: useHostTransitionStatus,
useFormState: rerenderActionState,
useActionState: rerenderActionState,
useOptimistic: rerenderOptimistic,
useMemoCache,
useCacheRefresh: updateRefresh,
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnRerender as Dispatcher).useEffectEvent = updateEvent;
}
十四、变量
1. 警告状态
备注
源码中 183 - 193 行
// 已警告组件挂钩不匹配
let didWarnAboutMismatchedHooksForComponent;
// 已警告未缓存的获取快照
let didWarnUncachedGetSnapshot: void | true;
let didWarnAboutUseWrappedInTryCatch;
// 已警告过使用 try-catch 包裹
let didWarnAboutAsyncClientComponent;
// 已警告关于使用表单状态
let didWarnAboutUseFormState;
if (__DEV__) {
didWarnAboutMismatchedHooksForComponent = new Set<string | null>();
didWarnAboutUseWrappedInTryCatch = new Set<string | null>();
didWarnAboutAsyncClientComponent = new Set<string | null>();
didWarnAboutUseFormState = new Set<string | null>();
}
2. en
备注
源码中 258 - 307 行
// These are set right before calling the component.
// 这些是在调用组件之前设置的。
let renderLanes: Lanes = NoLanes;
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
//
// 正在进行的 fiber。我给它起了不同的名字,以便与正在进行的 hook 区分开。
let currentlyRenderingFiber: Fiber = null as any;
// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.
//
// 钩子作为一个链表存储在 fiber 的 memoizedState 字段中。
// 当前的钩子列表属于当前的 fiber。
// 正在进行工作的钩子列表是一个新列表,将被添加到正在进行工作的 fiber 中。
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
// Whether an update was scheduled at any point during the render phase. This
// does not get reset if we do another render pass; only when we're completely
// finished evaluating this component. This is an optimization so we know
// whether we need to clear render phase updates after a throw.
//
// 渲染阶段的任何时候是否有计划更新。
// 如果我们进行另一轮渲染,这不会被重置;只有在我们完全
// 评估完这个组件时才会重置。这是一种优化,所以我们知道
// 在抛出异常后是否需要清除渲染阶段的更新。
let didScheduleRenderPhaseUpdate: boolean = false;
// Where an update was scheduled only during the current render pass. This
// gets reset after each attempt.
// TODO: Maybe there's some way to consolidate this with
// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.
//
// 在当前渲染过程期间仅计划了更新。
// 每次尝试后此值都会重置。
// TODO: 也许有某种方法可以将其与 `didScheduleRenderPhaseUpdate`
// 或 `numberOfReRenders` 合并。
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;
let shouldDoubleInvokeUserFnsInHooksDEV: boolean = false;
// Counts the number of useId hooks in this component.
// 统计此组件中 useId 钩子的数量。
let localIdCounter: number = 0;
// Counts number of `use`-d thenables
// 统计被 `use` 调用的 thenable 数量
let thenableIndexCounter: number = 0;
// 可则状态
let thenableState: ThenableState | null = null;
// Used for ids that are generated completely client-side (i.e. not during
// hydration). This counter is global, so client ids are not stable across
// render attempts.
//
// 用于完全在客户端生成的 id(即不在 hydration 期间生成)。
// 该计数器是全局的,因此客户端 id 在多次渲染尝试中不稳定。
let globalClientIdCounter: number = 0;
const RE_RENDER_LIMIT = 25;
// In DEV, this is the name of the currently executing primitive hook
// 在开发环境中,这是当前正在执行的原始钩子的名称
let currentHookNameInDev: ?HookType = null;
// In DEV, this list ensures that hooks are called in the same order between renders.
// The list stores the order of hooks used during the initial render (mount).
// Subsequent renders (updates) reference this list.
//
// 在开发环境中,这个列表确保钩子在每次渲染中以相同的顺序调用。
// 该列表存储了初次渲染(挂载)期间使用的钩子顺序。
// 后续渲染(更新)会参考该列表。
let hookTypesDev: Array<HookType> | null = null;
let hookTypesUpdateIndexDev: number = -1;
// In DEV, this tracks whether currently rendering component needs to ignore
// the dependencies for Hooks that need them (e.g. useEffect or useMemo).
// When true, such Hooks will always be "remounted". Only used during hot reload.
//
// 在开发环境中,这用于跟踪当前渲染的组件是否需要忽略那些依赖于 Hooks 的依赖
// 项(例如 useEffect 或 useMemo)。
// 当值为 true 时,这些 Hooks 将总是被“重新挂载”。仅在热重载期间使用。
let ignorePreviousDependencies: boolean = false;
十五、工具
1. 挂载钩子类型(开发)
function mountHookTypesDev(): void {
if (__DEV__) {
const hookName = currentHookNameInDev as any as HookType;
if (hookTypesDev === null) {
hookTypesDev = [hookName];
} else {
hookTypesDev.push(hookName);
}
}
}
2. 检查依赖是否为数组(开发)
function checkDepsAreArrayDev(deps: mixed): void {
if (__DEV__) {
if (deps !== undefined && deps !== null && !isArray(deps)) {
// Verify deps, but only on mount to avoid extra checks.
// It's unlikely their type would change as usually you define them inline.
//
// 验证依赖项,但只在挂载时进行,以避免额外检查。
// 它们的类型不太可能变化,因为通常你会直接在内联定义它们。
console.error(
'%s received a final argument that is not an array (instead, received `%s`). When ' +
'specified, the final argument must be an array.',
currentHookNameInDev,
typeof deps,
);
}
}
}
3. 更新 Hook 类型(开发)
function updateHookTypesDev(): void {
if (__DEV__) {
const hookName = ((currentHookNameInDev: any): HookType);
if (hookTypesDev !== null) {
hookTypesUpdateIndexDev++;
if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
warnOnHookMismatchInDev(hookName);
}
}
}
}
4. 在开发中警告 Hook 不匹配
:::note
- `getComponentNameFromFiber()` 由 [getComponentNameFromFiber] 实现
:::
function warnOnHookMismatchInDev(currentHookName: HookType): void {
if (__DEV__) {
const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
didWarnAboutMismatchedHooksForComponent.add(componentName);
if (hookTypesDev !== null) {
let table = '';
const secondColumnStart = 30;
for (let i = 0; i <= ((hookTypesUpdateIndexDev: any): number); i++) {
const oldHookName = hookTypesDev[i];
const newHookName =
i === ((hookTypesUpdateIndexDev: any): number)
? currentHookName
: oldHookName;
let row = `${i + 1}. ${oldHookName}`;
// Extra space so second column lines up
// lol @ IE not supporting String#repeat
while (row.length < secondColumnStart) {
row += ' ';
}
row += newHookName + '\n';
table += row;
}
console.error(
'React has detected a change in the order of Hooks called by %s. ' +
'This will lead to bugs and errors if not fixed. ' +
'For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
'%s' +
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',
componentName,
table,
);
}
}
}
}
5. 在开发中使用表单状态时发出警告
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
function warnOnUseFormStateInDev(): void {
if (__DEV__) {
const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
if (!didWarnAboutUseFormState.has(componentName)) {
didWarnAboutUseFormState.add(componentName);
console.error(
'ReactDOM.useFormState has been renamed to React.useActionState. ' +
'Please update %s to use React.useActionState.',
componentName,
);
}
}
}
6. 如果是异步客户端组件则发出警告
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
function warnIfAsyncClientComponent(Component: Function) {
if (__DEV__) {
// This dev-only check only works for detecting native async functions,
// not transpiled ones. There's also a prod check that we use to prevent
// async client components from crashing the app; the prod one works even
// for transpiled async functions. Neither mechanism is completely
// bulletproof but together they cover the most common cases.
//
// 这个仅用于开发的检查只适用于检测原生的异步函数,不适用于转译后的函数。我们还有一个
// 用于生产环境的检查,用来防止异步客户端组件导致应用崩溃;生产环境的检查即使对于转译后
// 的异步函数也有效。两种机制都不是完全万无一失,但结合起来可以覆盖最常见的情况。
const isAsyncFunction =
Object.prototype.toString.call(Component) === '[object AsyncFunction]' ||
Object.prototype.toString.call(Component) ===
'[object AsyncGeneratorFunction]';
if (isAsyncFunction) {
// Encountered an async Client Component. This is not yet supported.
// 遇到了异步客户端组件。此功能尚不支持。
const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
if (!didWarnAboutAsyncClientComponent.has(componentName)) {
didWarnAboutAsyncClientComponent.add(componentName);
console.error(
'%s is an async Client Component. ' +
'Only Server Components can be async at the moment. This error is often caused by accidentally ' +
"adding `'use client'` to a module that was originally written " +
'for the server.',
componentName === null
? 'An unknown Component'
: `<${componentName}>`,
);
}
}
}
}
7. 抛出无效钩子错误
function throwInvalidHookError() {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
);
}
8. Hook 输入是否相等
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
if (__DEV__) {
if (ignorePreviousDependencies) {
// Only true when this component is being hot reloaded.
// 只有在该组件被热重载时才为真。
return false;
}
}
if (prevDeps === null) {
if (__DEV__) {
console.error(
'%s received a final argument during this render, but not during ' +
'the previous render. Even though the final argument is optional, ' +
'its type cannot change between renders.',
currentHookNameInDev,
);
}
return false;
}
if (__DEV__) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
//
// 在生产环境中不必比较长度,因为这些数组应该直接内联传递。
if (nextDeps.length !== prevDeps.length) {
console.error(
'The final argument passed to %s changed size between renders. The ' +
'order and size of this array must remain constant.\n\n' +
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${prevDeps.join(', ')}]`,
`[${nextDeps.join(', ')}]`,
);
}
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
9. 再次使用钩子渲染
备注
callComponentInDEV()由 ReactFiberCallUserSpace#callComponentInDEV 实现
function renderWithHooksAgain<Props, SecondArg>(
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
): any {
// This is used to perform another render pass. It's used when setState is
// called during render, and for double invoking components in Strict Mode
// during development.
//
// 这用于执行另一次渲染。它在渲染期间调用 setState 时使用,并且在开发过程中用于
// 严格模式下的组件双重调用。
//
// The state from the previous pass is reused whenever possible. So, state
// updates that were already processed are not processed again, and memoized
// functions (`useMemo`) are not invoked again.
//
// 尽可能重复使用上一次传递的状态。因此,已经处理过的状态更新不会再次处理,缓存
// 的函数(`useMemo`)也不会再次调用。
//
// Keep rendering in a loop for as long as render phase updates continue to
// be scheduled. Use a counter to prevent infinite loops.
//
// 在渲染阶段更新继续被调度时,保持循环渲染。
// 使用计数器来防止无限循环。
currentlyRenderingFiber = workInProgress;
let numberOfReRenders: number = 0;
let children;
do {
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// It's possible that a use() value depended on a state that was updated in
// this rerender, so we need to watch for different thenables this time.
//
// 有可能 use() 的值依赖于在此次重新渲染中更新的状态,因此这次
// 我们需要关注不同的 thenable。
thenableState = null;
}
thenableIndexCounter = 0;
didScheduleRenderPhaseUpdateDuringThisPass = false;
if (numberOfReRenders >= RE_RENDER_LIMIT) {
throw new Error(
'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
// after first render to prevent infinite render phase updates.
//
// 即使在热重载时,也要允许依赖项在首次渲染后稳定
// 以防止无限渲染阶段更新。
ignorePreviousDependencies = false;
}
// Start over from the beginning of the list
// 从列表开头重新开始
currentHook = null;
workInProgressHook = null;
if (workInProgress.updateQueue != null) {
resetFunctionComponentUpdateQueue(workInProgress.updateQueue as any);
}
if (__DEV__) {
// Also validate hook order for cascading updates.
// 同时验证级联更新的钩子顺序。
hookTypesUpdateIndexDev = -1;
}
ReactSharedInternals.H = __DEV__
? HooksDispatcherOnRerenderInDEV
: HooksDispatcherOnRerender;
children = __DEV__
? callComponentInDEV(Component, props, secondArg)
: Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
return children;
}
10. 重置函数组件更新队列
function resetFunctionComponentUpdateQueue(
updateQueue: FunctionComponentUpdateQueue,
): void {
updateQueue.lastEffect = null;
updateQueue.events = null;
updateQueue.stores = null;
if (updateQueue.memoCache != null) {
// NOTE: this function intentionally does not reset memoCache data. We reuse updateQueue for the memo
// cache to avoid increasing the size of fibers that don't need a cache, but we don't want to reset
// the cache when other properties are reset.
//
// 注意:此函数故意不重置 memoCache 数据。我们重用 updateQueue 来缓存 memo
// 缓存,以避免增加不需要缓存的 fiber 的大小,但在重置其他属性时,我们不希望重置缓存。
updateQueue.memoCache.index = 0;
}
}
11. 完成渲染钩子
备注
checkIfWorkInProgressReceivedUpdate()由 ReactFiberBeginWork#checkIfWorkInProgressReceivedUpdate 实现checkIfContextChanged()由 ReactFiberNewContext#checkIfContextChanged 实现markWorkInProgressReceivedUpdate()由 ReactFiberBeginWork#markWorkInProgressReceivedUpdate 实现checkIfUseWrappedInTryCatch()由 ReactFiberThenable#checkIfUseWrappedInTryCatch 实现getComponentNameFromFiber()由 getComponentNameFromFiber 实现
function finishRenderingHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
): void {
if (__DEV__) {
workInProgress._debugHookTypes = hookTypesDev;
// Stash the thenable state for use by DevTools.
// 存储 thenable 状态以供 DevTools 使用。
if (workInProgress.dependencies === null) {
if (thenableState !== null) {
workInProgress.dependencies = {
lanes: NoLanes,
firstContext: null,
_debugThenableState: thenableState,
};
}
} else {
workInProgress.dependencies._debugThenableState = thenableState;
}
}
// 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-entrance.
//
// 我们可以假设前一个调度器总是这个,因为我们在渲染阶段开始时设置了它,并且没有再次进入。
ReactSharedInternals.H = ContextOnlyDispatcher;
// This check uses currentHook so that it works the same in DEV and prod bundles.
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
//
// 这个检查使用 currentHook,以确保它在开发和生产版本中表现一致。
// hookTypesDev 可以捕捉更多情况(例如 context),但仅在开发版本中。
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderLanes = NoLanes;
currentlyRenderingFiber = null as any;
currentHook = null;
workInProgressHook = null;
if (__DEV__) {
currentHookNameInDev = null;
hookTypesDev = null;
hookTypesUpdateIndexDev = -1;
// Confirm that a static flag was not added or removed since the last
// render. If this fires, it suggests that we incorrectly reset the static
// flags in some other part of the codebase. This has happened before, for
// example, in the SuspenseList implementation.
//
// 确认自上次渲染以来,静态标志没有被添加或移除。
// 如果触发此警告,说明我们在代码库的某个其他部分错误地重置了静态标志。
// 这种情况以前发生过,例如在 SuspenseList 的实现中。
if (
current !== null &&
(current.flags & StaticMaskEffect) !==
(workInProgress.flags & StaticMaskEffect) &&
// Disable this warning in legacy mode, because legacy Suspense is weird
// and creates false positives. To make this work in legacy mode, we'd
// need to mark fibers that commit in an incomplete state, somehow. For
// now I'll disable the warning that most of the bugs that would trigger
// it are either exclusive to concurrent mode or exist in both.
//
// 在旧版模式下禁用此警告,因为旧版 Suspense 很奇怪
// 并会产生误报。要在旧版模式下使其工作,我们需要
// 以某种方式标记以未完成状态提交的 Fiber。目前我会禁用此警告,
// 因为大多数会触发它的错误要么仅在并发模式下出现,要么两者都有。
(disableLegacyMode || (current.mode & ConcurrentMode) !== NoMode)
) {
console.error(
'Internal React error: Expected static flag was missing. Please ' +
'notify the React team.',
);
}
}
didScheduleRenderPhaseUpdate = false;
// This is reset by checkDidRenderIdHook
// 这是由 checkDidRenderIdHook 重置的
// localIdCounter = 0;
thenableIndexCounter = 0;
thenableState = null;
if (didRenderTooFewHooks) {
throw new Error(
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.',
);
}
if (current !== null) {
if (!checkIfWorkInProgressReceivedUpdate()) {
// If there were no changes to props or state, we need to check if there
// was a context change. We didn't already do this because there's no
// 1:1 correspondence between dependencies and hooks. Although, because
// there almost always is in the common case (`readContext` is an
// internal API), we could compare in there. OTOH, we only hit this case
// if everything else bails out, so on the whole it might be better to
// keep the comparison out of the common path.
//
// 如果 props 或 state 没有变化,我们需要检查上下文是否发生了变化。我们之前没有
// 这样做,因为依赖项和钩子之间没有一一对应关系。不过,在常见情况下几乎总是有这种
// 对应关系(`readContext` 是内部 API),我们可以在那里进行比较。另一方面,只有
// 在其他所有方式都失效时我们才会遇到这种情况,因此总体而言,最好将比较操作移出常规路径。
const currentDependencies = current.dependencies;
if (
currentDependencies !== null &&
checkIfContextChanged(currentDependencies)
) {
markWorkInProgressReceivedUpdate();
}
}
}
if (__DEV__) {
if (checkIfUseWrappedInTryCatch()) {
const componentName =
getComponentNameFromFiber(workInProgress) || 'Unknown';
if (
!didWarnAboutUseWrappedInTryCatch.has(componentName) &&
// This warning also fires if you suspend with `use` inside an
// async component. Since we warn for that above, we'll silence this
// second warning by checking here.
//
// 如果你在异步组件中使用 `use` 时挂起,也会触发此警告。
// 由于我们上面已经对此发出了警告,所以这里通过检查来屏蔽第二次警告。
!didWarnAboutAsyncClientComponent.has(componentName)
) {
didWarnAboutUseWrappedInTryCatch.add(componentName);
console.error(
'`use` was called from inside a try/catch block. This is not allowed ' +
'and can lead to unexpected behavior. To handle errors triggered ' +
'by `use`, wrap your component in a error boundary.',
);
}
}
}
}
12. 挂载工作进行中的钩子
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
// 这是列表中的第一个钩子
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
// 添加到列表末尾
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
13. 更新工作进行钩子
function updateWorkInProgressHook(): Hook {
// This function is used both for updates and for re-renders triggered by a
// render phase update. It assumes there is either a current hook we can
// clone, or a work-in-progress hook from a previous render pass that we can
// use as a base.
//
// 该函数既用于更新,也用于由渲染阶段更新触发的重新渲染。它假设要么有一个当前的 hook 可以
// 克隆,要么有一个来自之前渲染过程的正在进行的 hook 可以用作基础。
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
// 已经有一个正在进行的项目。可以复用它。
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
// 从当前钩子克隆。
if (nextCurrentHook === null) {
const currentFiber = currentlyRenderingFiber.alternate;
if (currentFiber === null) {
// This is the initial render. This branch is reached when the component
// suspends, resumes, then renders an additional hook.
// Should never be reached because we should switch to the mount dispatcher first.
//
// 这是初始渲染。当组件挂起、恢复,然后渲染一个额外的 Hook 时,会达到这个分支。
// 理论上不应达到这里,因为我们应当首先切换到挂载调度器。
throw new Error(
'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
);
} else {
// This is an update. We should always have a current hook.
// 这是一个更新。我们应该始终保持最新的 hook。
throw new Error('Rendered more hooks than during the previous render.');
}
}
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list.
// 这是列表中的第一个钩子。
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
// 添加到列表的末尾。
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
14. 创建函数组件更新队列
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
lastEffect: null,
events: null,
stores: null,
memoCache: null,
};
}
15. 使用可等待对象
备注
createThenableState()由 ReactFiberThenable#createThenableState 实现trackUsedThenable()由 ReactFiberThenable#trackUsedThenable 实现
function useThenable<T>(thenable: Thenable<T>): T {
// Track the position of the thenable within this fiber.
// 跟踪该可等待对象在此 fiber 中的位置。
const index = thenableIndexCounter;
thenableIndexCounter += 1;
if (thenableState === null) {
thenableState = createThenableState();
}
const result = trackUsedThenable(thenableState, thenable, index);
// When something suspends with `use`, we replay the component with the
// "re-render" dispatcher instead of the "mount" or "update" dispatcher.
//
// 当某些东西使用 `use` 挂起时,我们会使用“重新渲染”调度器重放组件,而不是“挂载”或“更新”调度器。
//
// But if there are additional hooks that occur after the `use` invocation
// that suspended, they wouldn't have been processed during the previous
// attempt. So after we invoke `use` again, we may need to switch from the
// "re-render" dispatcher back to the "mount" or "update" dispatcher. That's
// what the following logic accounts for.
//
// 但是如果在 `use` 调用之后还有其他挂钩被触发并且处于挂起状态,它们在之前的尝试中是不会被
// 处理的。因此,在我们再次调用 `use` 之后,可能需要从“重新渲染”调度器切换回“挂载”或“更新”调
// 度器。这就是下面逻辑所要处理的情况。
//
// TODO: Theoretically this logic only needs to go into the rerender
// dispatcher. Could optimize, but probably not be worth it.
//
// 待办:理论上这段逻辑只需要放在重新渲染调度器中即可。可以优化,但可能不值得。
// This is the same logic as in updateWorkInProgressHook.
// 这和 updateWorkInProgressHook 中的逻辑相同。
const workInProgressFiber = currentlyRenderingFiber;
const nextWorkInProgressHook =
workInProgressHook === null
? // We're at the beginning of the list, so read from the first hook from
// the fiber.
//
// 我们处在列表的开头,所以从 fiber 的第一个 hook 开始读取。
workInProgressFiber.memoizedState
: workInProgressHook.next;
if (nextWorkInProgressHook !== null) {
// There are still hooks remaining from the previous attempt.
// 之前尝试仍然留下了一些挂钩。
} else {
// There are no remaining hooks from the previous attempt. We're no longer
// in "re-render" mode. Switch to the normal mount or update dispatcher.
//
// 前一次尝试没有剩余的 hooks。我们不再处于“重新渲染”模式。切换到普通的挂载或更新分发器。
//
// This is the same as the logic in renderWithHooks, except we don't bother
// to track the hook types debug information in this case (sufficient to
// only do that when nothing suspends).
// 这与 renderWithHooks 中的逻辑相同,只是我们在这种情况下不去跟踪 hook 类型的
// 调试信息(只在没有任何挂起时才需要这样做)。
const currentFiber = workInProgressFiber.alternate;
if (__DEV__) {
if (currentFiber !== null && currentFiber.memoizedState !== null) {
ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;
} else {
ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
}
} else {
ReactSharedInternals.H =
currentFiber === null || currentFiber.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
}
return result;
}
16. 使用
备注
readContext()由 ReactFiberNewContext#readContext 实现
function use<T>(usable: Usable<T>): T {
if (usable !== null && typeof usable === 'object') {
if (typeof usable.then === 'function') {
// This is a thenable.
// 这是一个 thenable 对象。
const thenable: Thenable<T> = usable as any;
return useThenable(thenable);
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<T> = usable as any;
return readContext(context);
}
}
throw new Error('An unsupported type was passed to use(): ' + String(usable));
}
17. 使用记忆缓存
function useMemoCache(size: number): Array<mixed> {
let memoCache = null;
// Fast-path, load memo cache from wip fiber if already prepared
// 快速路径,如果已经准备好,则从正在处理的 fiber 加载缓存的备忘录
let updateQueue: FunctionComponentUpdateQueue | null =
currentlyRenderingFiber.updateQueue as any;
if (updateQueue !== null) {
memoCache = updateQueue.memoCache;
}
// Otherwise clone from the current fiber
// 否则从当前 fiber 克隆
if (memoCache == null) {
const current: Fiber | null = currentlyRenderingFiber.alternate;
if (current !== null) {
const currentUpdateQueue: FunctionComponentUpdateQueue | null =
current.updateQueue as any;
if (currentUpdateQueue !== null) {
const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache;
if (currentMemoCache != null) {
memoCache = {
// When enableNoCloningMemoCache is enabled, instead of treating the
// cache as copy-on-write, like we do with fibers, we share the same
// cache instance across all render attempts, even if the component
// is interrupted before it commits.
//
// 当启用 enableNoCloningMemoCache 时,不再将缓存视为写时复制(就像我们
// 对 fiber 做的那样),而是在所有渲染尝试中共享相同的缓存实例,即使组件在提交之前被中断。
//
// If an update is interrupted, either because it suspended or
// because of another update, we can reuse the memoized computations
// from the previous attempt. We can do this because the React
// Compiler performs atomic writes to the memo cache, i.e. it will
// not record the inputs to a memoization without also recording its
// output.
//
// 如果一次更新被中断,无论是因为它被挂起还是因为另一次更新,我们都可以重用
// 上一次尝试中记忆的计算结果。我们可以这样做是因为 React 编译器对记忆缓存
// 执行原子写入,也就是说,它不会仅记录记忆化的输入而不记录其输出。
//
// This gives us a form of "resuming" within components and hooks.
// 这为我们在组件和钩子中提供了一种“恢复”的方式。
//
// This only works when updating a component that already mounted.
// It has no impact during initial render, because the memo cache is
// stored on the fiber, and since we have not implemented resuming
// for fibers, it's always a fresh memo cache, anyway.
//
// 这只在更新已挂载的组件时有效。
// 在初次渲染时没有影响,因为 memo 缓存是存储在 fiber 上的,
// 由于我们还没有为 fibers 实现恢复功能,缓存总是全新的。
//
// However, this alone is pretty useful — it happens whenever you
// update the UI with fresh data after a mutation/action, which is
// extremely common in a Suspense-driven (e.g. RSC or Relay) app.
//
// 不过,仅仅这一点就已经非常有用——它发生在每次在执行变更/操作后用新数据
// 更新 UI 时,这在基于 Suspense(例如 RSC 或 Relay)的应用中非常常见。
data: enableNoCloningMemoCache
? currentMemoCache.data
: // Clone the memo cache before each render (copy-on-write)
// 在每次渲染前克隆 memo 缓存(写时复制)
currentMemoCache.data.map(array => array.slice()),
index: 0 as number,
};
}
}
}
}
// Finally fall back to allocating a fresh instance of the cache
// 最后回退到分配一个新的缓存实例
if (memoCache == null) {
memoCache = {
data: [],
index: 0 as number,
};
}
if (updateQueue === null) {
updateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = updateQueue;
}
updateQueue.memoCache = memoCache;
let data = memoCache.data[memoCache.index];
if (data === undefined || (__DEV__ && ignorePreviousDependencies)) {
data = memoCache.data[memoCache.index] = new Array(size);
for (let i = 0; i < size; i++) {
data[i] = REACT_MEMO_CACHE_SENTINEL;
}
} else if (data.length !== size) {
// TODO: consider warning or throwing here
// 待办:考虑在这里发出警告或抛出异常
if (__DEV__) {
console.error(
'Expected a constant size argument for each invocation of useMemoCache. ' +
'The previous cache was allocated with size %s but size %s was requested.',
data.length,
size,
);
}
}
memoCache.index++;
return data;
}
18. 基本状态简化器
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
19. 挂载 Reducer
备注
setIsStrictModeForDevtools()由 ReactFiberDevToolsHook#setIsStrictModeForDevtools 实现
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
init(initialArg);
} finally {
setIsStrictModeForDevtools(false);
}
}
} else {
initialState = initialArg as any as S;
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, A> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState as any,
};
hook.queue = queue;
const dispatch: Dispatch<A> = (queue.dispatch = dispatchReducerAction.bind(
null,
currentlyRenderingFiber,
queue,
) as any);
return [hook.memoizedState, dispatch];
}
20. 更新 Reducer
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
return updateReducerImpl(hook, ((currentHook as any) as Hook), reducer);
}
21. 更新 Reducer 实现
备注
removeLanes()由 ReactFiberLane#removeLanes 实现isSubsetOfLanes()由 ReactFiberLane#isSubsetOfLanes 实现getWorkInProgressRootRenderLanes()由 ReactFiberWorkLoop 提供isGestureRender()由 ReactFiberLane#isGestureRender 实现getWorkInProgressRoot()由 ReactFiberWorkLoop 提供mergeLanes()由 ReactFiberLane#mergeLanes 实现markSkippedUpdateLanes()由 ReactFiberWorkLoop 实现peekEntangledActionLane()由 ReactFiberAsyncAction#peekEntangledActionLane 实现markWorkInProgressReceivedUpdate()由 ReactFiberBeginWork#markWorkInProgressReceivedUpdate 实现peekEntangledActionThenable()由 ReactFiberAsyncAction#peekEntangledActionThenable 实现
function updateReducerImpl<S, A>(
hook: Hook,
current: Hook,
reducer: (S, A) => S,
): [S, Dispatch<A>] {
const queue = hook.queue;
if (queue === null) {
throw new Error(
'Should have a queue. You are likely calling Hooks conditionally, ' +
'which is not allowed. (https://react.dev/link/invalid-hook-call)',
);
}
queue.lastRenderedReducer = reducer;
// The last rebase update that is NOT part of the base state.
// 上一次不属于基础状态的 rebase 更新。
let baseQueue = hook.baseQueue;
// The last pending update that hasn't been processed yet.
// 尚未处理的最后一个待处理更新。
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// We have new updates that haven't been processed yet.
// We'll add them to the base queue.
//
// 我们有一些尚未处理的新更新。
// 我们会将它们加入基础队列。
if (baseQueue !== null) {
// Merge the pending queue and the base queue.
// 合并待处理队列和基础队列。
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
if (__DEV__) {
if (current.baseQueue !== baseQueue) {
// Internal invariant that should never happen, but feasibly could in
// the future if we implement resuming, or some form of that.
// 内部不变量,理论上永远不会发生,但在未来实现恢复功能或某种形式的恢复时,有可能发生。
console.error(
'Internal error: Expected work-in-progress queue to be a clone. ' +
'This is a bug in React.',
);
}
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
const baseState = hook.baseState;
if (baseQueue === null) {
// If there are no pending updates, then the memoized state should be the
// same as the base state. Currently these only diverge in the case of
// useOptimistic, because useOptimistic accepts a new baseState on
// every render.
//
// 如果没有待处理的更新,那么缓存的状态应该与基础状态相同。目前,只有在
// 使用 useOptimistic 的情况下它们才会不同,因为 useOptimistic 在
// 每次渲染时都会接收一个新的 baseState。
hook.memoizedState = baseState;
// We don't need to call markWorkInProgressReceivedUpdate because
// baseState is derived from other reactive values.
//
// 我们不需要调用 markWorkInProgressReceivedUpdate,因为 baseState 是从
// 其他响应式值派生的。
} else {
// We have a queue to process.
// 我们有一个队列需要处理。
const first = baseQueue.next;
let newState = baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast: Update<S, A> | null = null;
let update = first;
let didReadFromEntangledAsyncAction = false;
do {
// An extra OffscreenLane bit is added to updates that were made to
// a hidden tree, so that we can distinguish them from updates that were
// already there when the tree was hidden.
//
// 对隐藏树进行的更新会增加一个额外的 OffscreenLane 位,以便我们可以将它们
// 与树隐藏时已经存在的更新区分开来。
const updateLane = removeLanes(update.lane, OffscreenLane);
const isHiddenUpdate = updateLane !== update.lane;
// Check if this update was made while the tree was hidden. If so, then
// it's not a "base" update and we should disregard the extra base lanes
// that were added to renderLanes when we entered the Offscreen tree.
//
// 检查此更新是否在树隐藏时进行。如果是这样,
// 那么它不是“基础”更新,我们应该忽略在进入离屏树时
// 添加到 renderLanes 的额外基础车道。
let shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);
if (enableGestureTransition && updateLane === GestureLane) {
// This is a gesture optimistic update. It should only be considered as part of the
// rendered state while rendering the gesture lane and if the rendering the associated
// ScheduledGesture.
// 这是一个手势乐观更新。它只应在渲染手势通道以及渲染相关的已安排手势时,作为渲染状态的一部分考虑。
const scheduledGesture = update.gesture;
if (scheduledGesture !== null) {
if (scheduledGesture.count === 0) {
// This gesture has already been cancelled. We can clean up this update.
// 这个手势已经被取消。我们可以清理这个更新。
update = update.next;
continue;
} else if (!isGestureRender(renderLanes)) {
shouldSkipUpdate = true;
} else {
const root: FiberRoot | null = getWorkInProgressRoot();
if (root === null) {
throw new Error(
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
);
}
// We assume that the currently rendering gesture is the one first in the queue.
// 我们假设当前正在渲染的手势是队列中的第一个。
shouldSkipUpdate = root.pendingGestures !== scheduledGesture;
}
}
}
if (shouldSkipUpdate) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
//
// 优先级不足。跳过此更新。如果这是第一次跳过的更新,则之前的更新/状态是新的基础更新/状态。
const clone: Update<S, A> = {
lane: updateLane,
revertLane: update.revertLane,
gesture: update.gesture,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null as any,
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
//
// 更新队列中剩余的优先级。
// TODO: 不需要累积这个。相反,我们可以从原始车道中移除 renderLanes。
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
// This update does have sufficient priority.
// 这个更新确实有足够的优先级。
// Check if this is an optimistic update.
// 检查这是否是乐观更新。
const revertLane = update.revertLane;
if (revertLane === NoLane) {
// This is not an optimistic update, and we're going to apply it now.
// But, if there were earlier updates that were skipped, we need to
// leave this update in the queue so it can be rebased later.
//
// 这不是一个乐观更新,我们现在要应用它。
// 但是,如果之前有被跳过的更新,我们需要
// 将此更新保留在队列中,以便稍后可以重新基于它进行操作。
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
//
// 这个更新将会被提交,所以我们永远不想取消提交它。
// 使用 NoLane 可行,因为 0 是所有位掩码的子集,
// 所以上面的检查永远不会跳过它。
lane: NoLane,
revertLane: NoLane,
gesture: null,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null as any,
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Check if this update is part of a pending async action. If so,
// we'll need to suspend until the action has finished, so that it's
// batched together with future updates in the same action.
//
// 检查此更新是否属于待处理的异步操作。如果是,
// 我们需要暂停,直到操作完成,以便将其与同一操作中的未来更新一起批处理。
if (updateLane === peekEntangledActionLane()) {
didReadFromEntangledAsyncAction = true;
}
} else {
// This is an optimistic update. If the "revert" priority is
// sufficient, don't apply the update. Otherwise, apply the update,
// but leave it in the queue so it can be either reverted or
// rebased in a subsequent render.
//
// 这是一个乐观更新。如果“回退”优先级足够,请不要应用该更新。否则,应用该
// 更新,但将其保留在队列中,以便在后续渲染中可以回退或重新基准化。
if (isSubsetOfLanes(renderLanes, revertLane)) {
// The transition that this optimistic update is associated with
// has finished. Pretend the update doesn't exist by skipping
// over it.
//
// 与此乐观更新相关的转换已完成。通过跳过它,假装该更新不存在。
update = update.next;
// Check if this update is part of a pending async action. If so,
// we'll need to suspend until the action has finished, so that it's
// batched together with future updates in the same action.
//
// 检查此更新是否属于待处理的异步操作。如果是,
// 我们需要暂停,直到操作完成,以便将其与同一操作中的未来更新一起批处理。
if (revertLane === peekEntangledActionLane()) {
didReadFromEntangledAsyncAction = true;
}
continue;
} else {
const clone: Update<S, A> = {
// Once we commit an optimistic update, we shouldn't uncommit it
// until the transition it is associated with has finished
// (represented by revertLane). Using NoLane here works because 0
// is a subset of all bitmasks, so this will never be skipped by
// the check above.
//
// 一旦我们执行了乐观更新,就不应撤销它
// 直到与之关联的过渡完成
// (由 revertLane 表示)。在这里使用 NoLane 是可行的,因为 0
// 是所有位掩码的子集,因此上面的检查永远不会跳过它。
lane: NoLane,
// Reuse the same revertLane so we know when the transition
// has finished.
//
// 重用相同的 revertLane,这样我们就知道过渡何时完成。
revertLane: update.revertLane,
// 如果它提交了,就不再是手势更新。
gesture: null, // If it commits, it's no longer a gesture update.
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null as any,
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
//
// 更新队列中剩余的优先级。
// TODO: 不需要累积这个。相反,我们可以从原始
// 车道中移除 renderLanes。
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
revertLane,
);
markSkippedUpdateLanes(revertLane);
}
}
// Process this update.
// 处理此更新。
const action = update.action;
if (shouldDoubleInvokeUserFnsInHooksDEV) {
reducer(newState, action);
}
if (update.hasEagerState) {
// If this update is a state update (not a reducer) and was processed eagerly,
// we can use the eagerly computed state
//
// 如果这个更新是状态更新(而不是 reducer)并且已经被提前处理,
// 我们可以使用提前计算的状态
newState = update.eagerState as any as S;
} else {
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst as any;
}
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
//
// 标记该 fiber 执行了工作,但仅当新状态与当前状态不同的时候。
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
// Check if this update is part of a pending async action. If so, we'll
// need to suspend until the action has finished, so that it's batched
// together with future updates in the same action.
// TODO: Once we support hooks inside useMemo (or an equivalent
// memoization boundary like Forget), hoist this logic so that it only
// suspends if the memo boundary produces a new value.
//
// 检查此更新是否属于待处理的异步操作。如果是,我们需要挂起,直到该操作完成,这样
// 它就可以与同一操作中的后续更新一起批处理。
// TODO:一旦我们支持在 useMemo(或类似的记忆边界,如 Forget)内部使用 hooks,将此
// 逻辑提升,这样只有当记忆边界生成新值时才会挂起。
if (didReadFromEntangledAsyncAction) {
const entangledActionThenable = peekEntangledActionThenable();
if (entangledActionThenable !== null) {
// TODO: Instead of the throwing the thenable directly, throw a
// special object like `use` does so we can detect if it's captured
// by userspace.
//
// TODO:不要直接抛出 thenable,而是抛出类似 `use` 的特殊对象,这样
// 我们就可以检测它是否被用户空间捕获。
throw entangledActionThenable;
}
}
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
//
// `queue.lanes` 用于处理交错的过渡。一旦队列为空,我们可以将其重置为零。
queue.lanes = NoLanes;
}
const dispatch: Dispatch<A> = queue.dispatch as any;
return [hook.memoizedState, dispatch];
}
22. 重新渲染 Reducer
function rerenderReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
if (queue === null) {
throw new Error(
'Should have a queue. You are likely calling Hooks conditionally, ' +
'which is not allowed. (https://react.dev/link/invalid-hook-call)',
);
}
queue.lastRenderedReducer = reducer;
// This is a re-render. Apply the new render phase updates to the previous
// work-in-progress hook.
//
// 这是一次重新渲染。将新的渲染阶段更新应用到之前的进行中的 hook 上。
const dispatch: Dispatch<A> = queue.dispatch as 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.
//
// 标记该 fiber 执行了工作,但仅当新状态与当前状态不同时才标记。
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.
//
// 除非队列为空,否则不要将从渲染阶段更新中累积的状态持久化到基础状态。
// TODO:不确定这是否是预期的语义,但这正是我们对 gDSFP 所做的。我记不起原因了。
if (hook.baseQueue === null) {
hook.baseState = newState;
}
queue.lastRenderedState = newState;
}
return [newState, dispatch];
}
23. 挂载同步外部存储
备注
getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现getWorkInProgressRoot()由 ReactFiberWorkLoop 提供getWorkInProgressRootRenderLanes()由 ReactFiberWorkLoop 提供includesBlockingLane()由 ReactFiberLane#includesBlockingLane 实现
function mountSyncExternalStore<T>(
subscribe: (param: () => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
const fiber = currentlyRenderingFiber;
const hook = mountWorkInProgressHook();
let nextSnapshot;
const isHydrating = getIsHydrating();
if (isHydrating) {
if (getServerSnapshot === undefined) {
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
nextSnapshot = getServerSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
if (nextSnapshot !== getServerSnapshot()) {
console.error(
'The result of getServerSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
} else {
nextSnapshot = getSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
const cachedSnapshot = getSnapshot();
if (!is(nextSnapshot, cachedSnapshot)) {
console.error(
'The result of getSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
// Unless we're rendering a blocking lane, schedule a consistency check.
// Right before committing, we will walk the tree and check if any of the
// stores were mutated.
//
// 除非我们正在渲染一个阻塞的任务队列,否则安排一次一致性检查。
// 在提交之前,我们将遍历树并检查是否有任何存储被修改。
//
// We won't do this if we're hydrating server-rendered content, because if
// the content is stale, it's already visible anyway. Instead we'll patch
// it up in a passive effect.
//
// 如果我们在为服务器渲染的内容进行 hydration,就不会这样做,因为如果内容是过时的,它
// 已经可见了。我们会在一个被动效果中修补它。
const root: FiberRoot | null = getWorkInProgressRoot();
if (root === null) {
throw new Error(
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
);
}
const rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesBlockingLane(rootRenderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
// Read the current snapshot from the store on every render. This breaks the
// normal rules of React, and only works because store updates are
// always synchronous.
//
// 在每次渲染时从 store 中读取当前快照。这打破了 React 的常规规则,之
// 所以可行,是因为 store 的更新总是同步的。
hook.memoizedState = nextSnapshot;
const inst: StoreInstance<T> = {
value: nextSnapshot,
getSnapshot,
};
hook.queue = inst;
// Schedule an effect to subscribe to the store.
// 调度一个 effect 来订阅 store。
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);
// Schedule an effect to update the mutable instance fields. We will update
// this whenever subscribe, getSnapshot, or value changes. Because there's no
// clean-up function, and we track the deps correctly, we can call pushEffect
// directly, without storing any additional state. For the same reason, we
// don't need to set a static flag, either.
//
// 安排一个 effect 来更新可变的实例字段。每当 subscribe、getSnapshot 或 value 发生
// 变化时,我们都会更新它。由于没有清理函数,并且我们正确地跟踪了依赖项,因此可以直接
// 调用 pushEffect,而无需存储任何额外的状态。出于同样的原因,我们也不需要设置静态标志。
fiber.flags |= PassiveEffect;
pushSimpleEffect(
HookHasEffect | HookPassive,
createEffectInstance(),
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
null,
);
return nextSnapshot;
}
24. 更新同步外部存储
备注
getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现markWorkInProgressReceivedUpdate()由 ReactFiberBeginWork#markWorkInProgressReceivedUpdate 实现getWorkInProgressRoot()由 ReactFiberWorkLoop 提供includesBlockingLane()由 ReactFiberLane#includesBlockingLane 实现
function updateSyncExternalStore<T>(
subscribe: (param: () => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
const fiber = currentlyRenderingFiber;
const hook = updateWorkInProgressHook();
// Read the current snapshot from the store on every render. This breaks the
// normal rules of React, and only works because store updates are
// always synchronous.
//
// 在每次渲染时从 store 中读取当前快照。这打破了 React 的常规规则,之所以
// 可行,是因为 store 的更新总是同步的。
let nextSnapshot;
const isHydrating = getIsHydrating();
if (isHydrating) {
// Needed for strict mode double render
// 严格模式下双重渲染所需
if (getServerSnapshot === undefined) {
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
nextSnapshot = getServerSnapshot();
} else {
nextSnapshot = getSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
const cachedSnapshot = getSnapshot();
if (!is(nextSnapshot, cachedSnapshot)) {
console.error(
'The result of getSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
}
const prevSnapshot = (currentHook || hook).memoizedState;
const snapshotChanged = !is(prevSnapshot, nextSnapshot);
if (snapshotChanged) {
hook.memoizedState = nextSnapshot;
markWorkInProgressReceivedUpdate();
}
const inst = hook.queue;
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [
subscribe,
]);
// Whenever getSnapshot or subscribe changes, we need to check in the
// commit phase if there was an interleaved mutation. In concurrent mode
// this can happen all the time, but even in synchronous mode, an earlier
// effect may have mutated the store.
//
// 每当 getSnapshot 或 subscribe 发生变化时,我们需要在提交阶段检查是否有交错的变更。
// 在并发模式下,这种情况随时可能发生,但即使在同步模式下,早先的副作用也可能已修改了存储。
if (
inst.getSnapshot !== getSnapshot ||
snapshotChanged ||
// Check if the subscribe function changed. We can save some memory by
// checking whether we scheduled a subscription effect above.
//
// 检查订阅函数是否改变。通过检查我们是否在上面调度了订阅副作用,可以节省一些内存。
(workInProgressHook !== null &&
workInProgressHook.memoizedState.tag & HookHasEffect)
) {
fiber.flags |= PassiveEffect;
pushSimpleEffect(
HookHasEffect | HookPassive,
createEffectInstance(),
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
null,
);
// Unless we're rendering a blocking lane, schedule a consistency check.
// Right before committing, we will walk the tree and check if any of the
// stores were mutated.
//
// 除非我们正在渲染一个阻塞通道,否则安排一致性检查。
// 在提交之前,我们会遍历树并检查是否有任何存储被修改。
const root: FiberRoot | null = getWorkInProgressRoot();
if (root === null) {
throw new Error(
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
);
}
if (!isHydrating && !includesBlockingLane(renderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
return nextSnapshot;
}
25. 推送商店一致性检查
function pushStoreConsistencyCheck<T>(
fiber: Fiber,
getSnapshot: () => T,
renderedSnapshot: T,
): void {
fiber.flags |= StoreConsistency;
const check: StoreConsistencyCheck<T> = {
getSnapshot,
value: renderedSnapshot,
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue as any);
componentUpdateQueue.stores = [check];
} else {
const stores = componentUpdateQueue.stores;
if (stores === null) {
componentUpdateQueue.stores = [check];
} else {
stores.push(check);
}
}
}
26. 更新商店实例
function updateStoreInstance<T>(
fiber: Fiber,
inst: StoreInstance<T>,
nextSnapshot: T,
getSnapshot: () => T,
): void {
// These are updated in the passive phase
// 这些在被动阶段会更新
inst.value = nextSnapshot;
inst.getSnapshot = getSnapshot;
// Something may have been mutated in between render and commit. This could
// have been in an event that fired before the passive effects, or it could
// have been in a layout effect. In that case, we would have used the old
// snapsho and getSnapshot values to bail out. We need to check one more time.
//
// 渲染和提交之间可能发生了一些变动。可能是在被动效果执行之前触发的事件中发生的,也可能是在
// 布局效果中发生的。在这种情况下,我们会使用旧的快照和 getSnapshot 值来退出。我们需要再检查一次。
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
// We intentionally don't log update times and stacks here because this
// was not an external trigger but rather an internal one.
//
// 强制重新渲染。
// 我们有意不在这里记录更新时间和堆栈信息,因为这不是外部触发的,而是内部触发的。
forceStoreRerender(fiber);
}
}
27. 订阅商店
备注
startUpdateTimerByLane()由 ReactProfilerTimer#startUpdateTimerByLane 实现
function subscribeToStore<T>(
fiber: Fiber,
inst: StoreInstance<T>,
subscribe: (param: () => void) => () => void,
): any {
const handleStoreChange = () => {
// The store changed. Check if the snapshot changed since the last time we
// read from the store.
//
// 商店已更改。检查自上次从商店读取后快照是否已更改。
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
// 强制重新渲染。
startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()', fiber);
forceStoreRerender(fiber);
}
};
// Subscribe to the store and return a clean-up function.
// 订阅商店并返回一个清理函数。
return subscribe(handleStoreChange);
}
28. 检查快照是否已更改
function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {
const latestGetSnapshot = inst.getSnapshot;
const prevValue = inst.value;
try {
const nextValue = latestGetSnapshot();
return !is(prevValue, nextValue);
} catch (error) {
return true;
}
}
29. 强制重新渲染
备注
enqueueConcurrentRenderForLane()由 ReactFiberConcurrentUpdates#enqueueConcurrentRenderForLane 实现scheduleUpdateOnFiber()由 ReactFiberWorkLoop 提供
function forceStoreRerender(fiber: Fiber) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}
}
30. 挂载状态实现
备注
setIsStrictModeForDevtools()由 ReactFiberDevToolsHook#setIsStrictModeForDevtools 实现
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
const initialStateInitializer = initialState;
initialState = initialStateInitializer();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
initialStateInitializer();
} finally {
setIsStrictModeForDevtools(false);
}
}
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState as any,
};
hook.queue = queue;
return hook;
}
31. 挂载状态
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
const dispatch: Dispatch<BasicStateAction<S>> = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
) as any;
queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
32. 更新状态
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, initialState);
}
33. 重新渲染状态
function rerenderState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return rerenderReducer(basicStateReducer, initialState);
}
34. 挂载乐观
function mountOptimistic<S, A>(
passthrough: S,
reducer?: (S, A) => S,
): [S, (A) => void] {
const hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState = passthrough;
const queue: UpdateQueue<S, A> = {
pending: null,
lanes: NoLanes,
dispatch: null,
// Optimistic state does not use the eager update optimization.
// 乐观状态不使用急切更新优化。
lastRenderedReducer: null,
lastRenderedState: null,
};
hook.queue = queue;
// This is different than the normal setState function.
// 这与普通的 setState 函数不同。
const dispatch: (A) => void = dispatchOptimisticSetState.bind(
null,
currentlyRenderingFiber,
true,
queue,
) as any;
queue.dispatch = dispatch;
return [passthrough, dispatch];
}
35. 乐观更新
function updateOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
const hook = updateWorkInProgressHook();
return updateOptimisticImpl(
hook,
((currentHook: any): Hook),
passthrough,
reducer,
);
}
36. 乐观更新(实现)
function updateOptimisticImpl<S, A>(
hook: Hook,
current: Hook | null,
passthrough: S,
reducer?: (S, A) => S,
): [S, (A) => void] {
// Optimistic updates are always rebased on top of the latest value passed in
// as an argument. It's called a passthrough because if there are no pending
// updates, it will be returned as-is.
//
// 乐观更新总是基于传入的最新值进行重置。
// 它被称为透传,因为如果没有待处理的更新,它将原样返回。
//
// Reset the base state to the passthrough. Future updates will be applied
// on top of this.
// 将基础状态重置为直通模式。未来的更新将基于此进行。
hook.baseState = passthrough;
// If a reducer is not provided, default to the same one used by useState.
// 如果未提供 reducer,则默认为 useState 使用的相同 reducer。
const resolvedReducer: (S, A) => S =
typeof reducer === 'function' ? reducer : (basicStateReducer as any);
return updateReducerImpl(hook, currentHook as any as Hook, resolvedReducer);
}
37. 乐观重新渲染
function rerenderOptimistic<S, A>(
passthrough: S,
reducer: ?((S, A) => S),
): [S, (A) => void] {
// Unlike useState, useOptimistic doesn't support render phase updates.
// Also unlike useState, we need to replay all pending updates again in case
// the passthrough value changed.
//
// 不同于 useState,useOptimistic 不支持渲染阶段的更新。
// 也不同于 useState,如果 passthrough 值发生变化,我们需要重新执行所有待处理的更新。
//
// So instead of a forked re-render implementation that knows how to handle
// render phase udpates, we can use the same implementation as during a
// regular mount or update.
//
// 因此,我们可以使用与常规挂载或更新期间相同的实现,而不是使用一个分叉的重新渲染实现
// 来处理渲染阶段的更新。
const hook = updateWorkInProgressHook();
if (currentHook !== null) {
// This is an update. Process the update queue.
// 这是一次更新。处理更新队列。
return updateOptimisticImpl(
hook,
currentHook as any as Hook,
passthrough,
reducer,
);
}
// This is a mount. No updates to process.
// 这是一个挂载点。没有要处理的更新。
// Reset the base state to the passthrough. Future updates will be applied
// on top of this.
//
// 将基础状态重置为直通。未来的更新将基于此进行。
hook.baseState = passthrough;
const dispatch = hook.queue.dispatch;
return [passthrough, dispatch];
}
38. 分发操作状态
function dispatchActionState<S, P>(
fiber: Fiber,
actionQueue: ActionStateQueue<S, P>,
setPendingState: (param: boolean) => void,
setState: Dispatch<ActionStateQueueNode<S, P>>,
payload: P,
): void {
if (isRenderPhaseUpdate(fiber)) {
throw new Error('Cannot update form state while rendering.');
}
const currentAction = actionQueue.action;
if (currentAction === null) {
// An earlier action errored. Subsequent actions should not run.
// 先前的操作出错。后续操作不应运行。
return;
}
const actionNode: ActionStateQueueNode<S, P> = {
payload,
action: currentAction,
// 圆形的
next: null as any, // circular
isTransition: true,
status: 'pending',
value: null,
reason: null,
listeners: [],
then(listener) {
// We know the only thing that subscribes to these promises is `use` so
// this implementation is simpler than a generic thenable. E.g. we don't
// bother to check if the thenable is still pending because `use` already
// does that.
//
// 我们知道唯一会订阅这些 Promise 的是 `use`,所以
// 这个实现比通用的 thenable 更简单。例如,我们不
// 需要检查 thenable 是否仍处于 pending 状态,因为 `use` 已经
// 会处理这个问题。
actionNode.listeners.push(listener);
},
};
// Check if we're inside a transition. If so, we'll need to restore the
// transition context when the action is run.
//
// 检查我们是否处于过渡状态。如果是这样,当动作运行时,我们需要恢复过渡上下文。
const prevTransition = ReactSharedInternals.T;
if (prevTransition !== null) {
// Optimistically update the pending state, similar to useTransition.
// This will be reverted automatically when all actions are finished.
//
// 乐观地更新待处理状态,类似于 useTransition。
// 当所有操作完成时,这将会自动恢复。
setPendingState(true);
// `actionNode` is a thenable that resolves to the return value of
// the action.
//
// `actionNode` 是一个可以使用 .then 的对象,它会解析为动作的返回值。
setState(actionNode);
} else {
// This is not a transition.
// 这不是一个过渡。
actionNode.isTransition = false;
setState(actionNode);
}
const last = actionQueue.pending;
if (last === null) {
// There are no pending actions; this is the first one. We can run
// it immediately.
//
// 没有待处理的操作;这是第一个。我们可以立即运行它。
actionNode.next = actionQueue.pending = actionNode;
runActionStateAction(actionQueue, actionNode);
} else {
// There's already an action running. Add to the queue.
// 已经有一个操作在运行。加入到队列中。
const first = last.next;
actionNode.next = first;
actionQueue.pending = last.next = actionNode;
}
}
39. 运行动作状态动作
function runActionStateAction<S, P>(
actionQueue: ActionStateQueue<S, P>,
node: ActionStateQueueNode<S, P>,
) {
// `node.action` represents the action function at the time it was dispatched.
// If this action was queued, it might be stale, i.e. it's not necessarily the
// most current implementation of the action, stored on `actionQueue`. This is
// intentional. The conceptual model for queued actions is that they are
// queued in a remote worker; the dispatch happens immediately, only the
// execution is delayed.
//
// `node.action` 表示在分发时的动作函数。
// 如果此动作被排队,它可能是过时的,也就是说,它不一定是
// 存储在 `actionQueue` 上的动作的最新实现。这是有意为之。
// 队列动作的概念模型是它们在远程工作线程中排队;分发会立即发生,只有
// 执行会被延迟。
const action = node.action;
const payload = node.payload;
const prevState = actionQueue.state;
if (node.isTransition) {
// The original dispatch was part of a transition. We restore its
// transition context here.
//
// 原始的调度是过渡的一部分。我们在这里恢复它的过渡上下文。
// This is a fork of startTransition
// 这是 startTransition 的一个分支
const prevTransition = ReactSharedInternals.T;
const currentTransition: Transition = {} as any;
if (enableViewTransition) {
currentTransition.types =
prevTransition !== null
? // If we're a nested transition, we should use the same set as the parent
// since we're conceptually always joined into the same entangled transition.
// In practice, this only matters if we add transition types in the inner
// without setting state. In that case, the inner transition can finish
// without waiting for the outer.
//
// 如果我们是嵌套的过渡,我们应该使用与父过渡相同的集合
// 因为从概念上讲,我们总是加入同一个纠缠的过渡。
// 实际上,这只有在我们在内部添加过渡类型而没有设置状态时才重要。
// 在这种情况下,内部过渡可以在不等待外部过渡的情况下完成。
prevTransition.types
: null;
}
if (enableGestureTransition) {
currentTransition.gesture = null;
}
if (enableTransitionTracing) {
currentTransition.name = null;
currentTransition.startTime = -1;
}
if (__DEV__) {
currentTransition._updatedFibers = new Set();
}
ReactSharedInternals.T = currentTransition;
try {
const returnValue = action(prevState, payload);
const onStartTransitionFinish = ReactSharedInternals.S;
if (onStartTransitionFinish !== null) {
onStartTransitionFinish(currentTransition, returnValue);
}
handleActionReturnValue(actionQueue, node, returnValue);
} catch (error) {
onActionError(actionQueue, node, error);
} finally {
if (prevTransition !== null && currentTransition.types !== null) {
// If we created a new types set in the inner transition, we transfer it to the parent
// since they should share the same set. They're conceptually entangled.
//
// 如果我们在内部过渡中创建了一个新的类型集合,我们会将其传递给父级
// 因为它们应该共享同一个集合,本质上是相互关联的。
if (__DEV__) {
if (
prevTransition.types !== null &&
prevTransition.types !== currentTransition.types
) {
// Just assert that assumption holds that we're not overriding anything.
// 只是断言这个假设成立,即我们没有覆盖任何东西。
console.error(
'We expected inner Transitions to have transferred the outer types set and ' +
'that you cannot add to the outer Transition while inside the inner.' +
'This is a bug in React.',
);
}
}
prevTransition.types = currentTransition.types;
}
ReactSharedInternals.T = prevTransition;
if (__DEV__) {
if (prevTransition === null && currentTransition._updatedFibers) {
const updatedFibersCount = currentTransition._updatedFibers.size;
currentTransition._updatedFibers.clear();
if (updatedFibersCount > 10) {
console.warn(
'Detected a large number of updates inside startTransition. ' +
'If this is due to a subscription please re-write it to use React provided hooks. ' +
'Otherwise concurrent mode guarantees are off the table.',
);
}
}
}
}
} else {
// The original dispatch was not part of a transition.
// 原始分发不是过渡的一部分。
try {
const returnValue = action(prevState, payload);
handleActionReturnValue(actionQueue, node, returnValue);
} catch (error) {
onActionError(actionQueue, node, error);
}
}
}
40. 处理操作返回值
function handleActionReturnValue<S, P>(
actionQueue: ActionStateQueue<S, P>,
node: ActionStateQueueNode<S, P>,
returnValue: mixed,
) {
if (
returnValue !== null &&
typeof returnValue === 'object' &&
typeof returnValue.then === 'function'
) {
const thenable = returnValue as any as Thenable<Awaited<S>>;
if (__DEV__) {
// Keep track of the number of async transitions still running so we can warn.
// 跟踪仍在运行的异步转换数量,以便我们可以发出警告。
ReactSharedInternals.asyncTransitions++;
thenable.then(releaseAsyncTransition, releaseAsyncTransition);
}
// Attach a listener to read the return state of the action. As soon as
// this resolves, we can run the next action in the sequence.
//
// 附加一个监听器以读取操作的返回状态。一旦这个完成,我们就可以执行序列中的下一个操作。
thenable.then(
(nextState: Awaited<S>) => {
onActionSuccess(actionQueue, node, nextState);
},
(error: mixed) => onActionError(actionQueue, node, error),
);
if (__DEV__) {
if (!node.isTransition) {
console.error(
'An async function with useActionState was called outside of a transition. ' +
'This is likely not what you intended (for example, isPending will not update ' +
'correctly). Either call the returned function inside startTransition, or pass it ' +
'to an `action` or `formAction` prop.',
);
}
}
} else {
const nextState = returnValue as any as Awaited<S>;
onActionSuccess(actionQueue, node, nextState);
}
}
41. 操作成功
function onActionSuccess<S, P>(
actionQueue: ActionStateQueue<S, P>,
actionNode: ActionStateQueueNode<S, P>,
nextState: Awaited<S>,
) {
// The action finished running.
// 操作已完成运行。
actionNode.status = 'fulfilled';
actionNode.value = nextState;
notifyActionListeners(actionNode);
actionQueue.state = nextState;
// Pop the action from the queue and run the next pending action, if there
// are any.
// 从队列中弹出操作并运行下一个挂起的操作(如果有的话)。
const last = actionQueue.pending;
if (last !== null) {
const first = last.next;
if (first === last) {
// This was the last action in the queue.
// 这是队列中的最后一个操作。
actionQueue.pending = null;
} else {
// Remove the first node from the circular queue.
// 从循环队列中移除第一个节点。
const next = first.next;
last.next = next;
// Run the next action.
// 运行下一个操作。
runActionStateAction(actionQueue, next);
}
}
}
42. 操作错误
function onActionError<S, P>(
actionQueue: ActionStateQueue<S, P>,
actionNode: ActionStateQueueNode<S, P>,
error: mixed,
) {
// Mark all the following actions as rejected.
// 将以下所有操作标记为已拒绝。
const last = actionQueue.pending;
actionQueue.pending = null;
if (last !== null) {
const first = last.next;
do {
actionNode.status = 'rejected';
actionNode.reason = error;
notifyActionListeners(actionNode);
actionNode = actionNode.next;
} while (actionNode !== first);
}
// Prevent subsequent actions from being dispatched.
// 阻止后续操作被分发。
actionQueue.action = null;
}
43. 通知操作监听器
function notifyActionListeners<S, P>(actionNode: ActionStateQueueNode<S, P>) {
// Notify React that the action has finished.
// 通知 React 操作已完成。
const listeners = actionNode.listeners;
for (let i = 0; i < listeners.length; i++) {
// This is always a React internal listener, so we don't need to worry
// about it throwing.
//
// 这总是一个 React 内部监听器,所以我们不需要担心它会抛出异常。
const listener = listeners[i];
listener();
}
}
44. 动作状态简化器
function actionStateReducer<S>(oldState: S, newState: S): S {
return newState;
}
44. 挂载操作状态
备注
getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现getWorkInProgressRoot()由 ReactFiberWorkLoop 提供tryToClaimNextHydratableFormMarkerInstance()由 ReactFiberHydrationContext#tryToClaimNextHydratableFormMarkerInstance 实现
function mountActionState<S, P>(
action: (a: Awaited<S>, b: P) => S,
initialStateProp: Awaited<S>,
permalink?: string,
): [Awaited<S>, (a: P) => void, boolean] {
let initialState: Awaited<S> = initialStateProp;
if (getIsHydrating()) {
const root: FiberRoot = getWorkInProgressRoot() as any;
const ssrFormState = root.formState;
// If a formState option was passed to the root, there are form state
// markers that we need to hydrate. These indicate whether the form state
// matches this hook instance.
//
// 如果向根传递了 formState 选项,那么需要填充表单状态标记。这些标记指示
// 表单状态是否与此 hook 实例匹配。
if (ssrFormState !== null) {
const isMatching = tryToClaimNextHydratableFormMarkerInstance(
currentlyRenderingFiber,
);
if (isMatching) {
initialState = ssrFormState[0];
}
}
}
// State hook. The state is stored in a thenable which is then unwrapped by
// the `use` algorithm during render.
//
// 状态钩子。状态存储在一个可 then 的对象中,然后在渲染过程中由 `use` 算法解包。
const stateHook = mountWorkInProgressHook();
stateHook.memoizedState = stateHook.baseState = initialState;
// TODO: Typing this "correctly" results in recursion limit errors
// TODO:正确地输入这个会导致递归限制错误
// const stateQueue: UpdateQueue<S | Awaited<S>, S | Awaited<S>> = {
const stateQueue = {
pending: null,
lanes: NoLanes,
dispatch: null as any,
lastRenderedReducer: actionStateReducer,
lastRenderedState: initialState,
};
stateHook.queue = stateQueue;
const setState: Dispatch<S | Awaited<S>> = dispatchSetState.bind(
null,
currentlyRenderingFiber,
stateQueue as any as UpdateQueue<S | Awaited<S>, S | Awaited<S>>,
) as any;
stateQueue.dispatch = setState;
// Pending state. This is used to store the pending state of the action.
// Tracked optimistically, like a transition pending state.
//
// 待处理状态。用于存储操作的待处理状态。
// 以乐观方式跟踪,就像过渡的待处理状态一样。
const pendingStateHook = mountStateImpl(false as Thenable<boolean> | boolean);
const setPendingState: (param: boolean) => void =
dispatchOptimisticSetState.bind(
null,
currentlyRenderingFiber,
false,
pendingStateHook.queue as any as UpdateQueue<
S | Awaited<S>,
S | Awaited<S>
>,
) as any;
// Action queue hook. This is used to queue pending actions. The queue is
// shared between all instances of the hook. Similar to a regular state queue,
// but different because the actions are run sequentially, and they run in
// an event instead of during render.
//
// 操作队列钩子。用于将待处理操作排入队列。该队列在钩子的所有实例之间共享。类似于常规状态
// 队列,但不同之处在于操作是按顺序运行的,并且它们在事件中运行,而不是在渲染期间。
const actionQueueHook = mountWorkInProgressHook();
const actionQueue: ActionStateQueue<S, P> = {
state: initialState,
// 轮回
dispatch: null as any, // circular
action,
pending: null,
};
actionQueueHook.queue = actionQueue;
const dispatch = (dispatchActionState as any).bind(
null,
currentlyRenderingFiber,
actionQueue,
setPendingState,
setState,
);
actionQueue.dispatch = dispatch;
// Stash the action function on the memoized state of the hook. We'll use this
// to detect when the action function changes so we can update it in
// an effect.
//
// 将动作函数存储在 hook 的 memoized 状态中。我们将使用它来检测动作函数何时变化,从而
// 在 effect 中更新它。
actionQueueHook.memoizedState = action;
return [initialState, dispatch, false];
}
45. 更新操作状态
function updateActionState<S, P>(
action: (a: Awaited<S>, b: P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (a: P) => void, boolean] {
const stateHook = updateWorkInProgressHook();
const currentStateHook = currentHook as any as Hook;
return updateActionStateImpl(
stateHook,
currentStateHook,
action,
initialState,
permalink,
);
}
46. 更新操作状态(实现)
function updateActionStateImpl<S, P>(
stateHook: Hook,
currentStateHook: Hook,
action: (a: Awaited<S>, b: P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (a: P) => void, boolean] {
const [actionResult] = updateReducerImpl<S | Thenable<S>, S | Thenable<S>>(
stateHook,
currentStateHook,
actionStateReducer,
);
const [isPending] = updateState(false);
// This will suspend until the action finishes.
// 这将会挂起,直到操作完成。
let state: Awaited<S>;
if (
typeof actionResult === 'object' &&
actionResult !== null &&
typeof actionResult.then === 'function'
) {
try {
state = useThenable(actionResult as any as Thenable<Awaited<S>>);
} catch (x) {
if (x === SuspenseException) {
// If we Suspend here, mark this separately so that we can track this
// as an Action in Profiling tools.
//
// 如果我们在这里挂起,请单独标记,以便我们可以在分析工具中将其作为一个操作进行跟踪。
throw SuspenseActionException;
} else {
throw x;
}
}
} else {
state = actionResult as any;
}
const actionQueueHook = updateWorkInProgressHook();
const actionQueue = actionQueueHook.queue;
const dispatch = actionQueue.dispatch;
// Check if a new action was passed. If so, update it in an effect.
// 检查是否传入了新的操作。如果有,将其在副作用中更新。
const prevAction = actionQueueHook.memoizedState;
if (action !== prevAction) {
currentlyRenderingFiber.flags |= PassiveEffect;
pushSimpleEffect(
HookHasEffect | HookPassive,
createEffectInstance(),
actionStateActionEffect.bind(null, actionQueue, action),
null,
);
}
return [state, dispatch, isPending];
}
47. 动作状态效果
function actionStateActionEffect<S, P>(
actionQueue: ActionStateQueue<S, P>,
action: (a: Awaited<S>, b: P) => S,
): void {
actionQueue.action = action;
}
48. 重新渲染操作状态
function rerenderActionState<S, P>(
action: (a: Awaited<S>, b: P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (a: P) => void, boolean] {
// Unlike useState, useActionState doesn't support render phase updates.
// Also unlike useState, we need to replay all pending updates again in case
// the passthrough value changed.
//
// 不同于 useState,useActionState 不支持渲染阶段的更新。
// 也不同于 useState,如果透传值发生变化,我们需要重新执行所有待处理的更新。
//
// So instead of a forked re-render implementation that knows how to handle
// render phase udpates, we can use the same implementation as during a
// regular mount or update.
//
// 因此,我们可以使用与常规挂载或更新期间相同的实现,而不是使用一个分叉的重新渲染实现
// 来处理渲染阶段的更新。
const stateHook = updateWorkInProgressHook();
const currentStateHook = currentHook;
if (currentStateHook !== null) {
// This is an update. Process the update queue.
// 这是一次更新。处理更新队列。
return updateActionStateImpl(
stateHook,
currentStateHook,
action,
initialState,
permalink,
);
}
// 状态
updateWorkInProgressHook(); // State
// This is a mount. No updates to process.
// 这是一个挂载点。没有要处理的更新。
const state: Awaited<S> = stateHook.memoizedState;
const actionQueueHook = updateWorkInProgressHook();
const actionQueue = actionQueueHook.queue;
const dispatch = actionQueue.dispatch;
// This may have changed during the rerender.
// 在重新渲染过程中,这可能已经改变。
actionQueueHook.memoizedState = action;
// For mount, pending is always false.
// 对于挂载,挂起始终为假。
return [state, dispatch, false];
}
49. 简单推送效果
function pushSimpleEffect(
tag: HookFlags,
inst: EffectInstance,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): Effect {
const effect: Effect = {
tag,
create,
deps,
inst,
// Circular
// 轮回
next: null as any,
};
return pushEffectImpl(effect);
}
50. 简单推送效果(实现)
function pushEffectImpl(effect: Effect): Effect {
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
currentlyRenderingFiber.updateQueue as any;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;
}
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
return effect;
}
51. 创建效果实例
function createEffectInstance(): EffectInstance {
return { destroy: undefined };
}
52. 挂载 ref
function mountRef<T>(initialValue: T): { current: T } {
const hook = mountWorkInProgressHook();
const ref = { current: initialValue };
hook.memoizedState = ref;
return ref;
}
53. 更新 ref
function updateRef<T>(initialValue: T): { current: T } {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
54. 挂载效果实现
function mountEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hookFlags,
createEffectInstance(),
create,
nextDeps,
);
}
55. 更新效果实现
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
// currentHook is null on initial mount when rerendering after a render phase
// state update or for strict mode.
//
// 在初始挂载时,如果在渲染阶段之后重新渲染或处于严格模式,currentHook 为 null。
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushSimpleEffect(
hookFlags,
inst,
create,
nextDeps,
);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hookFlags,
inst,
create,
nextDeps,
);
}
56. 挂载 effect
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
) {
mountEffectImpl(
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
} else {
mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
}
57. 更新 effect
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
58. useEffect 事件(实现)
// function useEffectEventImpl<Args, Return, F:(...Array<Args>) => Return>(
function useEffectEventImpl<Args, Return, F>(
payload: EventFunctionPayload<Args, Return, F>,
) {
currentlyRenderingFiber.flags |= UpdateEffect;
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
currentlyRenderingFiber.updateQueue as any;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;
componentUpdateQueue.events = [payload];
} else {
const events = componentUpdateQueue.events;
if (events === null) {
componentUpdateQueue.events = [payload];
} else {
events.push(payload);
}
}
}
59. 挂载 event
备注
isInvalidExecutionContextForEventFunction()由 ReactFiberWorkLoop 提供
function mountEvent<Args, Return, F>(
// function mountEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
const hook = mountWorkInProgressHook();
const ref = { impl: callback };
hook.memoizedState = ref;
return function eventFn() {
if (isInvalidExecutionContextForEventFunction()) {
throw new Error(
"A function wrapped in useEffectEvent can't be called during rendering.",
);
}
return ref.impl.apply(undefined, arguments);
};
}
60. 更新 event
备注
isInvalidExecutionContextForEventFunction()由 ReactFiberWorkLoop 提供
function updateEvent<Args, Return, F>(
// function updateEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
const hook = updateWorkInProgressHook();
const ref = hook.memoizedState;
useEffectEventImpl({ ref, nextImpl: callback });
return function eventFn() {
if (isInvalidExecutionContextForEventFunction()) {
throw new Error(
"A function wrapped in useEffectEvent can't be called during rendering.",
);
}
return ref.impl.apply(undefined, arguments);
};
}
61. 挂载插入效果
function mountInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
mountEffectImpl(UpdateEffect, HookInsertion, create, deps);
}
62. 更新插入效果
function updateInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(UpdateEffect, HookInsertion, create, deps);
}
63. 挂载布局副作用
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
) {
fiberFlags |= MountLayoutDevEffect;
}
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
64. 更新布局效果
function updateLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
65. 自定义引用效果
function imperativeHandleEffect<T>(
create: () => T,
ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,
): void | (() => void) {
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
const refCleanup = refCallback(inst);
return () => {
if (typeof refCleanup === 'function') {
refCleanup();
} else {
refCallback(null);
}
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
if (__DEV__) {
if (!refObject.hasOwnProperty('current')) {
console.error(
'Expected useImperativeHandle() first argument to either be a ' +
'ref callback or React.createRef() object. Instead received: %s.',
'an object with keys {' + Object.keys(refObject).join(', ') + '}',
);
}
}
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
66. 挂载指令句柄
function mountImperativeHandle<T>(
ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
if (typeof create !== 'function') {
console.error(
'Expected useImperativeHandle() second argument to be a function ' +
'that creates a handle. Instead received: %s.',
create !== null ? typeof create : 'null',
);
}
}
// TODO: If deps are provided, should we skip comparing the ref itself?
// TODO: 如果提供了依赖项,我们是否应该跳过比较引用本身?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
) {
fiberFlags |= MountLayoutDevEffect;
}
mountEffectImpl(
fiberFlags,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
67. 更新指令句柄
function updateImperativeHandle<T>(
ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
if (typeof create !== 'function') {
console.error(
'Expected useImperativeHandle() second argument to be a function ' +
'that creates a handle. Instead received: %s.',
create !== null ? typeof create : 'null',
);
}
}
// TODO: If deps are provided, should we skip comparing the ref itself?
// TODO: 如果提供了依赖项,我们是否应该跳过比较引用本身?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
updateEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
68. 挂载调试值
function mountDebugValue<T>(
value: T,
formatterFn: ?((value: T) => mixed),
): void {
// This hook is normally a no-op.
// The react-debug-hooks package injects its own implementation
// so that e.g. DevTools can display custom hook values.
//
// 这个钩子通常是无操作的。
// react-debug-hooks 包会注入它自己的实现
// 以便例如 DevTools 可以显示自定义钩子值。
}
69. 挂载回调
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
70. 更新回调
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
71. 挂载 memo
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
nextCreate();
} finally {
setIsStrictModeForDevtools(false);
}
}
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
72. 更新 memo
备注
setIsStrictModeForDevtools()由 ReactFiberDevToolsHook#setIsStrictModeForDevtools 实现
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// Assume these are defined. If they're not, areHookInputsEqual will warn.
// 假设这些已被定义。如果没有,areHookInputsEqual 将会发出警告。
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
const nextValue = nextCreate();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
nextCreate();
} finally {
setIsStrictModeForDevtools(false);
}
}
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
73. 挂载延迟值
function mountDeferredValue<T>(value: T, initialValue?: T): T {
const hook = mountWorkInProgressHook();
return mountDeferredValueImpl(hook, value, initialValue);
}
74. 更新延迟值
function updateDeferredValue<T>(value: T, initialValue?: T): T {
const hook = updateWorkInProgressHook();
const resolvedCurrentHook: Hook = currentHook as any;
const prevValue: T = resolvedCurrentHook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value, initialValue);
}
75. 重新渲染延迟值
function rerenderDeferredValue<T>(value: T, initialValue?: T): T {
const hook = updateWorkInProgressHook();
if (currentHook === null) {
// This is a rerender during a mount.
// 这是在挂载期间的重新渲染。
return mountDeferredValueImpl(hook, value, initialValue);
} else {
// This is a rerender during an update.
const prevValue: T = currentHook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value, initialValue);
}
}
76. 正在延迟渲染工作
备注
includesSomeLane()由 ReactFiberLane#includesSomeLane 实现getWorkInProgressRootRenderLanes()由 ReactFiberWorkLoop 提供
function isRenderingDeferredWork(): boolean {
if (!includesSomeLane(renderLanes, DeferredLane)) {
// 没有任何渲染通道是延迟通道。
// None of the render lanes are deferred lanes.
return false;
}
// At least one of the render lanes are deferred lanes. However, if the
// current render is also batched together with an update, then we can't
// say that the render is wholly the result of deferred work. We can check
// this by checking if the root render lanes contain any "update" lanes, i.e.
// lanes that are only assigned to updates, like setState.
//
// 至少有一个渲染通道是延迟通道。然而,如果当前渲染也与更新一起批处理,那么我们不能说渲染
// 完全是延迟工作的结果。我们可以通过检查根渲染通道是否包含任何“更新”通道来验证这一点,即
// 仅分配给更新的通道,例如 setState。
const rootRenderLanes = getWorkInProgressRootRenderLanes();
return !includesSomeLane(rootRenderLanes, UpdateLanes);
}
77. 挂载延迟值(实现)
备注
requestDeferredLane()由 ReactFiberWorkLoop 提供mergeLanes()由 ReactFiberLane#mergeLanes 实现markSkippedUpdateLanes()由 ReactFiberWorkLoop 提供
function mountDeferredValueImpl<T>(hook: Hook, value: T, initialValue?: T): T {
if (
// When `initialValue` is provided, we defer the initial render even if the
// current render is not synchronous.
//
// 当提供 `initialValue` 时,即使当前渲染不是同步的,我们也会推迟初始渲染。
initialValue !== undefined &&
// However, to avoid waterfalls, we do not defer if this render
// was itself spawned by an earlier useDeferredValue. Check if DeferredLane
// is part of the render lanes.
//
// 但是,为了避免瀑布效应,如果这个渲染本身是由之前的 useDeferredValue 触发的,我们
// 不会延迟。检查 DeferredLane 是否是渲染通道的一部分。
!isRenderingDeferredWork()
) {
// Render with the initial value
// 使用初始值进行渲染
hook.memoizedState = initialValue;
// Schedule a deferred render to switch to the final value.
// 安排一个延迟渲染以切换到最终值。
const deferredLane = requestDeferredLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
);
markSkippedUpdateLanes(deferredLane);
return initialValue;
} else {
hook.memoizedState = value;
return value;
}
}
78. 更新延迟值(实现)
备注
isCurrentTreeHidden()由 ReactFiberHiddenContext#isCurrentTreeHidden 实现markWorkInProgressReceivedUpdate()由 ReactFiberBeginWork#markWorkInProgressReceivedUpdate 实现includesOnlyNonUrgentLanes()由 ReactFiberLane#includesOnlyNonUrgentLanes 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现markSkippedUpdateLanes()由 ReactFiberWorkLoop 提供markWorkInProgressReceivedUpdate()由 ReactFiberWorkLoop 提供
function updateDeferredValueImpl<T>(
hook: Hook,
prevValue: T,
value: T,
initialValue?: T,
): T {
if (is(value, prevValue)) {
// The incoming value is referentially identical to the currently rendered
// value, so we can bail out quickly.
//
// 传入的值与当前渲染的值在引用上是相同的,因此我们可以快速退出。
return value;
} else {
// Received a new value that's different from the current value.
// 收到了一个与当前值不同的新值。
// Check if we're inside a hidden tree
if (isCurrentTreeHidden()) {
// Revealing a prerendered tree is considered the same as mounting new
// one, so we reuse the "mount" path in this case.
// 显示预渲染的树被认为等同于挂载新的树,因此在这种情况下我们重用“挂载”路径。
const resultValue = mountDeferredValueImpl(hook, value, initialValue);
// Unlike during an actual mount, we need to mark this as an update if
// the value changed.
// 与实际挂载不同,如果值发生变化,我们需要将其标记为更新。
if (!is(resultValue, prevValue)) {
markWorkInProgressReceivedUpdate();
}
return resultValue;
}
const shouldDeferValue =
!includesOnlyNonUrgentLanes(renderLanes) && !isRenderingDeferredWork();
if (shouldDeferValue) {
// This is an urgent update. Since the value has changed, keep using the
// previous value and spawn a deferred render to update it later.
//
// 这是一个紧急更新。由于值已更改,请继续使用先前的值,并生成一个延迟渲染以稍后更新它。
// Schedule a deferred render
// 安排延迟渲染
const deferredLane = requestDeferredLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
);
markSkippedUpdateLanes(deferredLane);
// Reuse the previous value. We do not need to mark this as an update,
// because we did not render a new value.
//
// 重用先前的值。我们不需要将其标记为更新,
// 因为我们没有渲染一个新值。
return prevValue;
} else {
// This is not an urgent update, so we can use the latest value regardless
// of what it is. No need to defer it.
//
// 这不是紧急更新,所以我们可以使用最新的值,不管它是什么。没有必要推迟。
// Mark this as an update to prevent the fiber from bailing out.
// 将此标记为更新,以防止 fiber 放弃。
markWorkInProgressReceivedUpdate();
hook.memoizedState = value;
return value;
}
}
}
79. 释放异步过渡
function releaseAsyncTransition() {
if (__DEV__) {
ReactSharedInternals.asyncTransitions--;
}
}
80. 开始转换
备注
getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供higherEventPriority()由 ReactEventPriorities#higherEventPriority 实现chainThenableValue()由 ReactFiberAsyncAction#chainThenableValue 实现requestUpdateLane()由 ReactFiberWorkLoop 提供
function startTransition<S>(
fiber: Fiber,
queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>,
pendingState: S,
finishedState: S,
callback: () => mixed,
options?: StartTransitionOptions,
): void {
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(
higherEventPriority(previousPriority, ContinuousEventPriority),
);
const prevTransition = ReactSharedInternals.T;
const currentTransition: Transition = {} as any;
if (enableViewTransition) {
currentTransition.types =
prevTransition !== null
? // If we're a nested transition, we should use the same set as the parent
// since we're conceptually always joined into the same entangled transition.
// In practice, this only matters if we add transition types in the inner
// without setting state. In that case, the inner transition can finish
// without waiting for the outer.
//
// 如果我们是嵌套的过渡,我们应该使用与父过渡相同的集合
// 因为从概念上讲,我们总是加入同一个纠缠的过渡。
// 实际上,这只有在我们在内部添加过渡类型而没有设置状态时才重要。
// 在这种情况下,内部过渡可以在不等待外部过渡的情况下完成。
prevTransition.types
: null;
}
if (enableGestureTransition) {
currentTransition.gesture = null;
}
if (enableTransitionTracing) {
currentTransition.name =
options !== undefined && options.name !== undefined ? options.name : null;
currentTransition.startTime = now();
}
if (__DEV__) {
currentTransition._updatedFibers = new Set();
}
// We don't really need to use an optimistic update here, because we
// schedule a second "revert" update below (which we use to suspend the
// transition until the async action scope has finished). But we'll use an
// optimistic update anyway to make it less likely the behavior accidentally
// diverges; for example, both an optimistic update and this one should
// share the same lane.
//
// 我们这里其实不需要使用乐观更新,因为我们在下面安排了第二个“回退”更新(我们用它来暂停过渡,直到
// 异步操作范围完成)。但我们还是会使用乐观更新,以减少行为意外偏离的可能性;例如,乐观更新和这个
// 更新应该共享相同的通道。
ReactSharedInternals.T = currentTransition;
dispatchOptimisticSetState(fiber, false, queue, pendingState);
try {
const returnValue = callback();
const onStartTransitionFinish = ReactSharedInternals.S;
if (onStartTransitionFinish !== null) {
onStartTransitionFinish(currentTransition, returnValue);
}
// Check if we're inside an async action scope. If so, we'll entangle
// this new action with the existing scope.
//
// 检查我们是否在一个异步操作范围内。如果是,我们将把这个新操作与现有范围关联起来。
//
// If we're not already inside an async action scope, and this action is
// async, then we'll create a new async scope.
//
// 如果我们还没有在异步操作的范围内,并且此操作是异步的,那么我们将创建一个新的异步范围。
//
// In the async case, the resulting render will suspend until the async
// action scope has finished.
//
// 在异步情况下,生成的渲染将会暂停,直到异步操作范围完成。
if (
returnValue !== null &&
typeof returnValue === 'object' &&
typeof returnValue.then === 'function'
) {
const thenable = returnValue as any as Thenable<mixed>;
if (__DEV__) {
// Keep track of the number of async transitions still running so we can warn.
// 跟踪仍在运行的异步转换数量,以便我们可以发出警告。
ReactSharedInternals.asyncTransitions++;
thenable.then(releaseAsyncTransition, releaseAsyncTransition);
}
// Create a thenable that resolves to `finishedState` once the async
// action has completed.
//
// 创建一个 thenable,当异步操作完成后解析为 `finishedState`。
const thenableForFinishedState = chainThenableValue(
thenable,
finishedState,
);
dispatchSetStateInternal(
fiber,
queue,
thenableForFinishedState as any,
requestUpdateLane(fiber),
);
} else {
dispatchSetStateInternal(
fiber,
queue,
finishedState,
requestUpdateLane(fiber),
);
}
} catch (error) {
// This is a trick to get the `useTransition` hook to rethrow the error.
// When it unwraps the thenable with the `use` algorithm, the error
// will be thrown.
//
// 这是一个让 `useTransition` 钩子重新抛出错误的技巧。
// 当它使用 `use` 算法展开 thenable 时,错误
// 就会被抛出。
const rejectedThenable: RejectedThenable<S> = {
then() {},
status: 'rejected',
reason: error,
};
dispatchSetStateInternal(
fiber,
queue,
rejectedThenable,
requestUpdateLane(fiber),
);
} finally {
setCurrentUpdatePriority(previousPriority);
if (prevTransition !== null && currentTransition.types !== null) {
// If we created a new types set in the inner transition, we transfer it to the parent
// since they should share the same set. They're conceptually entangled.
//
// 如果我们在内部过渡中创建了一个新的类型集合,我们会将其传递给父级
// 因为它们应该共享同一个集合,本质上是相互关联的。
if (__DEV__) {
if (
prevTransition.types !== null &&
prevTransition.types !== currentTransition.types
) {
// Just assert that assumption holds that we're not overriding anything.
// 只是断言这个假设成立,即我们没有覆盖任何东西。
console.error(
'We expected inner Transitions to have transferred the outer types set and ' +
'that you cannot add to the outer Transition while inside the inner.' +
'This is a bug in React.',
);
}
}
prevTransition.types = currentTransition.types;
}
ReactSharedInternals.T = prevTransition;
if (__DEV__) {
if (prevTransition === null && currentTransition._updatedFibers) {
const updatedFibersCount = currentTransition._updatedFibers.size;
currentTransition._updatedFibers.clear();
if (updatedFibersCount > 10) {
console.warn(
'Detected a large number of updates inside startTransition. ' +
'If this is due to a subscription please re-write it to use React provided hooks. ' +
'Otherwise concurrent mode guarantees are off the table.',
);
}
}
}
}
}
81. 确保表单组件是有状态的
function ensureFormComponentIsStateful(formFiber: Fiber) {
const existingStateHook: Hook | null = formFiber.memoizedState;
if (existingStateHook !== null) {
// This fiber was already upgraded to be stateful.
// 这个 fiber 已经升级为有状态的。
return existingStateHook;
}
// Upgrade this host component fiber to be stateful. We're going to pretend
// it was stateful all along so we can reuse most of the implementation
// for function components and useTransition.
//
// 将此宿主组件 fiber 升级为有状态组件。我们将假装它一直是有状态的,以便可以重用大部分
// 用于函数组件和 useTransition 的实现。
//
// Create the state hook used by TransitionAwareHostComponent. This is
// essentially an inlined version of mountState.
// 创建 TransitionAwareHostComponent 使用的状态钩子。
// 这本质上是 mountState 的内联版本。
const newQueue: UpdateQueue<
Thenable<TransitionStatus> | TransitionStatus,
BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>
> = {
pending: null,
lanes: NoLanes,
// We're going to cheat and intentionally not create a bound dispatch
// method, because we can call it directly in startTransition.
//
// 我们打算作弊,故意不创建绑定的调度方法,
// 因为我们可以直接在 startTransition 中调用它。
dispatch: null as any,
lastRenderedReducer: basicStateReducer,
lastRenderedState: NoPendingHostTransition,
};
const stateHook: Hook = {
memoizedState: NoPendingHostTransition,
baseState: NoPendingHostTransition,
baseQueue: null,
queue: newQueue,
next: null,
};
// We use another state hook to track whether the form needs to be reset.
// The state is an empty object. To trigger a reset, we update the state
// to a new object. Then during rendering, we detect that the state has
// changed and schedule a commit effect.
//
// 我们使用另一个状态钩子来跟踪表单是否需要重置。
// 状态是一个空对象。要触发重置,我们将状态更新为一个新对象。
// 然后在渲染过程中,我们检测到状态已改变并安排提交副作用。
const initialResetState = {};
const newResetStateQueue: UpdateQueue<Object, Object> = {
pending: null,
lanes: NoLanes,
// We're going to cheat and intentionally not create a bound dispatch
// method, because we can call it directly in startTransition.
//
// 我们打算作弊,故意不创建绑定的调度方法,
// 因为我们可以直接在 startTransition 中调用它。
dispatch: null as any,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialResetState,
};
const resetStateHook: Hook = {
memoizedState: initialResetState,
baseState: initialResetState,
baseQueue: null,
queue: newResetStateQueue,
next: null,
};
stateHook.next = resetStateHook;
// Add the hook list to both fiber alternates. The idea is that the fiber
// had this hook all along.
//
// 将钩子列表添加到两个 fiber 备用节点。其想法是该 fiber 从一开始就有这个钩子。
formFiber.memoizedState = stateHook;
const alternate = formFiber.alternate;
if (alternate !== null) {
alternate.memoizedState = stateHook;
}
return stateHook;
}
82. 挂载过渡
function mountTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const stateHook = mountStateImpl(false as Thenable<boolean> | boolean);
// The `start` method never changes.
// `start` 方法永远不会改变。
const start = startTransition.bind(
null,
currentlyRenderingFiber,
stateHook.queue,
true,
false,
);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [false, start];
}
83. 更新过渡
function updateTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [booleanOrThenable] = updateState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
const isPending =
typeof booleanOrThenable === 'boolean'
? booleanOrThenable
: // This will suspend until the async action scope has finished.
// 这将会挂起,直到异步操作范围完成。
useThenable(booleanOrThenable);
return [isPending, start];
}
84. 重新渲染过渡
function rerenderTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [booleanOrThenable] = rerenderState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
const isPending =
typeof booleanOrThenable === 'boolean'
? booleanOrThenable
: // This will suspend until the async action scope has finished.
// 这将会挂起,直到异步操作范围完成。
useThenable(booleanOrThenable);
return [isPending, start];
}
85. 使用宿主环境过渡状态
备注
readContext()由 ReactFiberNewContext#readContext 实现
function useHostTransitionStatus(): TransitionStatus {
return readContext(HostTransitionContext);
}
86. 挂载工作进行中的钩子
备注
getWorkInProgressRoot()由 ReactFiberWorkLoop 提供getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现getTreeId()由 ReactFiberTreeContext#getTreeId 实现
function mountId(): string {
const hook = mountWorkInProgressHook();
const root = getWorkInProgressRoot() as any as FiberRoot;
// TODO: In Fizz, id generation is specific to each server config. Maybe we
// should do this in Fiber, too? Deferring this decision for now because
// there's no other place to store the prefix except for an internal field on
// the public createRoot object, which the fiber tree does not currently have
// a reference to.
//
// 待办事项:在 Fizz 中,ID 生成是针对每个服务器配置特定的。也许我们在 Fiber 中也应该这样做?
// 暂时推迟这个决定,因为除了公共 createRoot 对象的一个内部字段之外,没有其他地方可以存储前缀,
// 而 fiber 树目前没有对此的引用。
const identifierPrefix = root.identifierPrefix;
let id;
if (getIsHydrating()) {
const treeId = getTreeId();
// Use a captial R prefix for server-generated ids.
// 对于服务器生成的 ID,请使用大写 R 前缀。
id = '_' + identifierPrefix + 'R_' + treeId;
// Unless this is the first id at this level, append a number at the end
// that represents the position of this useId hook among all the useId
// hooks for this fiber.
//
// 除非这是该级别的第一个 id,否则在末尾添加一个数字
// 该数字表示此 useId 钩子在该 fiber 的所有 useId 钩子中的位置。
const localId = localIdCounter++;
if (localId > 0) {
id += 'H' + localId.toString(32);
}
id += '_';
} else {
// Use a lowercase r prefix for client-generated ids.
// 对客户端生成的 ID 使用小写的 r 前缀。
const globalClientId = globalClientIdCounter++;
id = '_' + identifierPrefix + 'r_' + globalClientId.toString(32) + '_';
}
hook.memoizedState = id;
return id;
}
87. 更新 ID
function updateId(): string {
const hook = updateWorkInProgressHook();
const id: string = hook.memoizedState;
return id;
}
88. 挂载刷新
function mountRefresh(): any {
const hook = mountWorkInProgressHook();
const refresh = (hook.memoizedState = refreshCache.bind(
null,
currentlyRenderingFiber,
));
return refresh;
}
89. 更新刷新
function updateRefresh(): any {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
90. 刷新缓存
备注
requestUpdateLane()由 ReactFiberWorkLoop 提供createLegacyQueueUpdate()由 ReactFiberClassUpdateQueue#createUpdate 实现enqueueLegacyQueueUpdate()由 ReactFiberClassUpdateQueue#enqueueUpdate 实现requestUpdateLane()由 [scheduleUpdateOnFiber] 提供startUpdateTimerByLane()由 ReactProfilerTimer#startUpdateTimerByLane 实现entangleLegacyQueueTransitions()由 ReactFiberClassUpdateQueue#entangleTransitions 实现createCache()由 ReactFiberCacheComponent#createCache 实现
function refreshCache<T>(
fiber: Fiber,
seedKey: ?(() => T),
seedValue: T,
): void {
// TODO: Does Cache work in legacy mode? Should decide and write a test.
// TODO: Consider warning if the refresh is at discrete priority, or if we
// otherwise suspect that it wasn't batched properly.
//
// 待办事项:缓存在旧模式下是否有效?应决定并编写测试。
// 待办事项:如果刷新处于离散优先级,或者我们怀疑它没有正确批处理,应考虑发出警告。
let provider = fiber.return;
while (provider !== null) {
switch (provider.tag) {
case CacheComponent:
case HostRoot: {
// Schedule an update on the cache boundary to trigger a refresh.
// 在缓存边界安排一次更新以触发刷新。
const lane = requestUpdateLane(provider);
const refreshUpdate = createLegacyQueueUpdate(lane);
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
if (root !== null) {
startUpdateTimerByLane(lane, 'refresh()', fiber);
scheduleUpdateOnFiber(root, provider, lane);
entangleLegacyQueueTransitions(root, provider, lane);
}
// TODO: If a refresh never commits, the new cache created here must be
// released. A simple case is start refreshing a cache boundary, but then
// unmount that boundary before the refresh completes.
//
// 待办事项:如果刷新从未提交,则此处创建的新缓存必须释放。一个简单的例子是
// 开始刷新缓存边界,但在刷新完成之前卸载该边界。
const seededCache = createCache();
if (seedKey !== null && seedKey !== undefined && root !== null) {
if (enableLegacyCache) {
// Seed the cache with the value passed by the caller. This could be
// from a server mutation, or it could be a streaming response.
//
// 使用调用者传入的值初始化缓存。这可以来自服务器变更,也可以来自流式响应。
seededCache.data.set(seedKey, seedValue);
} else {
if (__DEV__) {
console.error(
'The seed argument is not enabled outside experimental channels.',
);
}
}
}
const payload = {
cache: seededCache,
};
refreshUpdate.payload = payload;
return;
}
}
provider = provider.return;
}
// TODO: Warn if unmounted?
// 待办事项:如果未挂载,是否要发出警告?
}
91. 分发 Reducer 动作
备注
requestUpdateLane()由 ReactFiberWorkLoop 提供enqueueConcurrentHookUpdate()由 ReactFiberConcurrentUpdates#enqueueConcurrentHookUpdate 实现startUpdateTimerByLane()由 ReactProfilerTimer#startUpdateTimerByLane 实现scheduleUpdateOnFiber()由 ReactFiberWorkLoop 提供
function dispatchReducerAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
): void {
if (__DEV__) {
// using a reference to `arguments` bails out of GCC optimizations which affect function arity
// 使用对 `arguments` 的引用会影响 GCC 的优化,从而影响函数的参数个数
const args = arguments;
if (typeof args[3] === 'function') {
console.error(
"State updates from the useState() and useReducer() Hooks don't support the " +
'second callback argument. To execute a side effect after ' +
'rendering, declare it in the component body with useEffect().',
);
}
}
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
revertLane: NoLane,
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null as any,
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
startUpdateTimerByLane(lane, 'dispatch()', fiber);
scheduleUpdateOnFiber(root, fiber, lane);
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane, action);
}
92. 派发设置状态
备注
requestUpdateLane()由 ReactFiberWorkLoop 提供startUpdateTimerByLane()由 ReactProfilerTimer#startUpdateTimerByLane 实现
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
): void {
if (__DEV__) {
// using a reference to `arguments` bails out of GCC optimizations which affect function arity
// 使用对 `arguments` 的引用会影响 GCC 的优化,从而影响函数的参数个数
const args = arguments;
if (typeof args[3] === 'function') {
console.error(
"State updates from the useState() and useReducer() Hooks don't support the " +
'second callback argument. To execute a side effect after ' +
'rendering, declare it in the component body with useEffect().',
);
}
}
const lane = requestUpdateLane(fiber);
const didScheduleUpdate = dispatchSetStateInternal(
fiber,
queue,
action,
lane,
);
if (didScheduleUpdate) {
startUpdateTimerByLane(lane, 'setState()', fiber);
}
markUpdateInDevTools(fiber, lane, action);
}
93. 内部派发设置状态
备注
enqueueConcurrentHookUpdateAndEagerlyBailout()由 ReactFiberConcurrentUpdates#enqueueConcurrentHookUpdateAndEagerlyBailout 实现enqueueConcurrentHookUpdate()由 ReactFiberConcurrentUpdates#enqueueConcurrentHookUpdate 实现scheduleUpdateOnFiber()由 ReactFiberWorkLoop 提供
function dispatchSetStateInternal<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
lane: Lane,
): boolean {
const update: Update<S, A> = {
lane,
revertLane: NoLane,
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null as any,
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
//
// 队列当前为空,这意味着我们可以在进入渲染阶段之前积极计算下一状态。如果
// 新状态与当前状态相同,我们可能完全可以跳过。
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher = null;
if (__DEV__) {
prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
const currentState: S = queue.lastRenderedState as any;
const eagerState = lastRenderedReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
//
// 将急切计算的状态以及用于计算它的 reducer 存储在更新对象上。如果在进入渲染阶段
// 时 reducer 没有变化,那么可以直接使用急切状态,而无需再次调用 reducer。
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
// TODO: Do we still need to entangle transitions in this case?
//
// 快速路径。我们可以在不安排 React 重新渲染的情况下直接退出。
// 仍然有可能我们之后需要重新应用这个更新,
// 如果组件因为其他原因重新渲染,到那时 reducer 已经改变。
// TODO:在这种情况下我们是否仍然需要将过渡纠缠起来?
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return false;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
// 抑制错误。它将在渲染阶段再次抛出。
} finally {
if (__DEV__) {
ReactSharedInternals.H = prevDispatcher;
}
}
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
entangleTransitionUpdate(root, queue, lane);
return true;
}
}
return false;
}
94. 派发乐观状态设置
备注
requestCurrentTransition()由 ReactFiberTransition#requestCurrentTransition 实现peekEntangledActionLane()由 ReactFiberAsyncAction#peekEntangledActionLane 实现requestTransitionLane()由 ReactFiberRootScheduler#requestTransitionLane 实现enqueueConcurrentHookUpdate()由 ReactFiberConcurrentUpdates#enqueueConcurrentHookUpdate 实现startUpdateTimerByLane()由 ReactProfilerTimer#startUpdateTimerByLane 实现scheduleUpdateOnFiber()由 ReactFiberWorkLoop 提供scheduleGesture()由 ReactFiberGestureScheduler#scheduleGesture 实现
function dispatchOptimisticSetState<S, A>(
fiber: Fiber,
throwIfDuringRender: boolean,
queue: UpdateQueue<S, A>,
action: A,
): void {
const transition = requestCurrentTransition();
if (__DEV__) {
if (transition === null) {
// An optimistic update occurred, but startTransition is not on the stack.
// There are two likely scenarios.
//
// 发生了乐观更新,但 startTransition 不在堆栈上。
// 有两种可能的情况。
// One possibility is that the optimistic update is triggered by a regular
// event handler (e.g. `onSubmit`) instead of an action. This is a mistake
// and we will warn.
//
// 一种可能性是乐观更新是由常规事件处理程序(例如 `onSubmit`)触发的,而不是由动作触发
// 的。这是一个错误,我们将发出警告。
// The other possibility is the optimistic update is inside an async
// action, but after an `await`. In this case, we can make it "just work"
// by associating the optimistic update with the pending async action.
//
// 另一种可能性是乐观更新在异步操作中,但发生在 `await` 之后。在这种情况下,我们可以
// 通过将乐观更新与待处理的异步操作关联起来,使其“正常工作”。
// Technically it's possible that the optimistic update is unrelated to
// the pending action, but we don't have a way of knowing this for sure
// because browsers currently do not provide a way to track async scope.
// (The AsyncContext proposal, if it lands, will solve this in the
// future.) However, this is no different than the problem of unrelated
// transitions being grouped together — it's not wrong per se, but it's
// not ideal.
//
// 从技术上讲,乐观更新可能与待处理操作无关,
// 但我们无法确定这一点,因为浏览器目前没有提供跟踪异步作用域的方法。
// (如果 AsyncContext 提案实现,将来会解决这个问题。)
// 然而,这与将不相关的过渡分组在一起的问题没有本质区别——它本身不是错误,但并不理想。
// Once AsyncContext starts landing in browsers, we will provide better
// warnings in development for these cases.
//
// 一旦 AsyncContext 开始在浏览器中使用,我们将在开发中为这些情况提供更好的警告。
if (peekEntangledActionLane() !== NoLane) {
// There is a pending async action. Don't warn.
// 有一个待处理的异步操作。不要发出警告。
} else {
// There's no pending async action. The most likely cause is that we're
// inside a regular event handler (e.g. onSubmit) instead of an action.
//
// 没有挂起的异步操作。最可能的原因是我们在常规事件处理程序(例如 onSubmit)中,而
// 不是在一个 action 中。
console.error(
'An optimistic state update occurred outside a transition or ' +
'action. To fix, move the update to an action, or wrap ' +
'with startTransition.',
);
}
}
}
// For regular Transitions an optimistic update commits synchronously.
// For gesture Transitions an optimistic update commits on the GestureLane.
//
// 对于常规过渡,乐观更新会同步提交。
// 对于手势过渡,乐观更新会在手势通道上提交。
const lane =
enableGestureTransition && transition !== null && transition.gesture
? GestureLane
: SyncLane;
const update: Update<S, A> = {
lane: lane,
// After committing, the optimistic update is "reverted" using the same
// lane as the transition it's associated with.
//
// 提交后,会使用与其关联的过渡相同的通道“回退”乐观更新。
revertLane: requestTransitionLane(transition),
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null as any,
};
if (isRenderPhaseUpdate(fiber)) {
// When calling startTransition during render, this warns instead of
// throwing because throwing would be a breaking change. setOptimisticState
// is a new API so it's OK to throw.
//
// 在渲染期间调用 startTransition 时,会发出警告而不是抛出异常,
// 因为抛出异常会导致破坏性更改。setOptimisticState 是新的 API,
// 所以抛出异常是可以的。
if (throwIfDuringRender) {
throw new Error('Cannot update optimistic state while rendering.');
} else {
// startTransition was called during render. We don't need to do anything
// besides warn here because the render phase update would be overidden by
// the second update, anyway. We can remove this branch and make it throw
// in a future release.
//
// startTransition 在渲染过程中被调用。我们不需要做任何事情,除了在这里发出警告,因为
// 渲染阶段的更新无论如何都会被第二次更新覆盖。我们可以在将来的版本中移除这个分支并让它抛出错误。
if (__DEV__) {
console.error('Cannot call startTransition while rendering.');
}
}
} else {
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
// NOTE: The optimistic update implementation assumes that the transition
// will never be attempted before the optimistic update. This currently
// holds because the optimistic update is always synchronous. If we ever
// change that, we'll need to account for this.
//
// 注意:乐观更新的实现假设在乐观更新之前不会尝试进行过渡。目前这是成立的,因为
// 乐观更新总是同步进行的。如果我们将来改变这一点,就需要考虑这一点。
startUpdateTimerByLane(lane, 'setOptimistic()', fiber);
scheduleUpdateOnFiber(root, fiber, lane);
// Optimistic updates are always synchronous, so we don't need to call
// entangleTransitionUpdate here.
//
// 乐观更新总是同步的,所以我们不需要在这里调用 entangleTransitionUpdate。
if (enableGestureTransition && transition !== null) {
const provider = transition.gesture;
if (provider !== null) {
// If this was a gesture, ensure we have a scheduled gesture and that
// we associate this update with this specific gesture instance.
//
// 如果这是一个手势,确保我们有一个预定的手势,并且将此更新与该特定手势实例关联。
update.gesture = scheduleGesture(root, provider);
}
}
}
}
markUpdateInDevTools(fiber, lane, action);
}
95. 渲染阶段更新
function isRenderPhaseUpdate(fiber: Fiber): boolean {
const alternate = fiber.alternate;
return (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
);
}
96. 入队渲染阶段更新
function enqueueRenderPhaseUpdate<S, A>(
queue: UpdateQueue<S, A>,
update: Update<S, A>,
): void {
// This is a render phase update. Stash it in a lazily-created map of
// 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.
//
// 这是一个渲染阶段的更新。将其存放在一个延迟创建的映射中,映射的结构是
// 队列 -> 更新
// 的链表。在这次渲染之后,我们将重新启动,并将存放的更新应用到正在进行的 hook 之上。
didScheduleRenderPhaseUpdateDuringThisPass =
didScheduleRenderPhaseUpdate = true;
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;
}
97. 纠缠过渡更新
备注
isTransitionLane()由 ReactFiberLane#isTransitionLane 实现intersectLanes()由 ReactFiberLane#intersectLanes 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现markRootEntangled()由 ReactFiberLane#markRootEntangled 实现
// TODO: Move to ReactFiberConcurrentUpdates?
// 待办:移动到 ReactFiberConcurrentUpdates?
function entangleTransitionUpdate<S, A>(
root: FiberRoot,
queue: UpdateQueue<S, A>,
lane: Lane,
): void {
if (isTransitionLane(lane)) {
let queueLanes = queue.lanes;
// If any entangled lanes are no longer pending on the root, then they
// must have finished. We can remove them from the shared queue, which
// represents a superset of the actually pending lanes. In some cases we
// may entangle more than we need to, but that's OK. In fact it's worse if
// we *don't* entangle when we should.
//
// 如果任何纠缠的车道不再依赖于根,那么它们一定已经完成。我们可以将它们从共享队列中移除,该
// 队列代表了实际待处理车道的超集。在某些情况下,我们可能会纠缠超出所需的部分,但没关系。实
// 际上,如果在应该纠缠时我们没有纠缠,情况会更糟。
queueLanes = intersectLanes(queueLanes, root.pendingLanes);
// Entangle the new transition lane with the other transition lanes.
// 将新的过渡车道与其他过渡车道交织在一起。
const newQueueLanes = mergeLanes(queueLanes, lane);
queue.lanes = newQueueLanes;
// Even if queue.lanes already include lane, we don't know for certain if
// the lane finished since the last time we entangled it. So we need to
// entangle it again, just to be sure.
//
// 即使 queue.lanes 已经包含了该 lane,我们也无法确定自上次将其交织以来该 lane 是否
// 已经完成。因此我们需要再次交织它,以确保万无一失。
markRootEntangled(root, newQueueLanes);
}
}
98. 在开发者工具中标记更新
备注
markStateUpdateScheduled()由 ReactFiberDevToolsHook#markStateUpdateScheduled 实现
function markUpdateInDevTools<A>(fiber: Fiber, lane: Lane, action: A): void {
if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane);
}
}
十六、开发环境模拟
开发环境模拟代码
// 在开发环境中挂载 Hooks 调度器
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
// DEV环境中带钩子类型的 HooksDispatcher 挂载
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
// 开发环境中 HooksDispatcher 更新
let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
// 开发环境中 HooksDispatcher 重新渲染
let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
// 开发环境中挂载时的无效嵌套 Hooks 调度器
let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
// 开发环境中更新时的无效嵌套 Hooks 调度器
let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
// 在开发模式下重新渲染时的无效嵌套 Hooks 调度器
let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
if (__DEV__) {
const warnInvalidContextAccess = () => {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
};
const warnInvalidHookAccess = () => {
console.error(
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
'You can only call Hooks at the top level of your React function. ' +
'For more information, see ' +
'https://react.dev/link/rules-of-hooks',
);
};
HooksDispatcherOnMountInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
mountHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
mountHookTypesDev();
return mountRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
mountHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
mountHookTypesDev();
return mountDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
mountHookTypesDev();
return mountTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
mountHookTypesDev();
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
mountHookTypesDev();
return mountId();
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
mountHookTypesDev();
warnOnUseFormStateInDev();
return mountActionState(action, initialState, permalink);
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
mountHookTypesDev();
return mountActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
mountHookTypesDev();
return mountOptimistic(passthrough, reducer);
},
useHostTransitionStatus,
useMemoCache,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
mountHookTypesDev();
return mountRefresh();
},
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
mountHookTypesDev();
return mountEvent(callback);
};
}
HooksDispatcherOnMountWithHookTypesInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
updateHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
updateHookTypesDev();
return mountEffect(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 mountImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
updateHookTypesDev();
return mountInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
updateHookTypesDev();
return mountLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
updateHookTypesDev();
return mountRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
updateHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
return mountDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
updateHookTypesDev();
return mountTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
updateHookTypesDev();
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
updateHookTypesDev();
return mountId();
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
updateHookTypesDev();
return mountActionState(action, initialState, permalink);
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
warnOnUseFormStateInDev();
return mountActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
updateHookTypesDev();
return mountOptimistic(passthrough, reducer);
},
useHostTransitionStatus,
useMemoCache,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return mountRefresh();
},
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
updateHookTypesDev();
return mountEvent(callback);
};
}
HooksDispatcherOnUpdateInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
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);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
updateHookTypesDev();
return updateInsertionEffect(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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = 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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
return updateDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
updateHookTypesDev();
return updateTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
updateHookTypesDev();
return updateId();
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
warnOnUseFormStateInDev();
return updateActionState(action, initialState, permalink);
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
updateHookTypesDev();
return updateActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
updateHookTypesDev();
return updateOptimistic(passthrough, reducer);
},
useHostTransitionStatus,
useMemoCache,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
},
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
updateHookTypesDev();
return updateEvent(callback);
};
}
HooksDispatcherOnRerenderInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
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);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
updateHookTypesDev();
return updateInsertionEffect(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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return rerenderReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = 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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return rerenderState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
return rerenderDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
updateHookTypesDev();
return rerenderTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
updateHookTypesDev();
return updateId();
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
warnOnUseFormStateInDev();
return rerenderActionState(action, initialState, permalink);
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
updateHookTypesDev();
return rerenderActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
updateHookTypesDev();
return rerenderOptimistic(passthrough, reducer);
},
useHostTransitionStatus,
useMemoCache,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
},
};
if (enableUseEffectEventHook) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
updateHookTypesDev();
return updateEvent(callback);
};
}
InvalidNestedHooksDispatcherOnMountInDEV = {
readContext<T>(context: ReactContext<T>): T {
warnInvalidContextAccess();
return readContext(context);
},
use<T>(usable: Usable<T>): T {
warnInvalidHookAccess();
return use(usable);
},
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
warnInvalidHookAccess();
mountHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
mountHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountEffect(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();
mountHookTypesDev();
return mountImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
warnInvalidHookAccess();
mountHookTypesDev();
return mountRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
warnInvalidHookAccess();
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
mountHookTypesDev();
return mountDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
warnInvalidHookAccess();
mountHookTypesDev();
return mountTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
warnInvalidHookAccess();
mountHookTypesDev();
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
warnInvalidHookAccess();
mountHookTypesDev();
return mountId();
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
mountHookTypesDev();
return mountActionState(action, initialState, permalink);
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
warnInvalidHookAccess();
mountHookTypesDev();
return mountActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
warnInvalidHookAccess();
mountHookTypesDev();
return mountOptimistic(passthrough, reducer);
},
useMemoCache(size: number): Array<any> {
warnInvalidHookAccess();
return useMemoCache(size);
},
useHostTransitionStatus,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
mountHookTypesDev();
return mountRefresh();
},
};
if (enableUseEffectEventHook) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
warnInvalidHookAccess();
mountHookTypesDev();
return mountEvent(callback);
};
}
InvalidNestedHooksDispatcherOnUpdateInDEV = {
readContext<T>(context: ReactContext<T>): T {
warnInvalidContextAccess();
return readContext(context);
},
use<T>(usable: Usable<T>): T {
warnInvalidHookAccess();
return use(usable);
},
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
warnInvalidHookAccess();
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return readContext(context);
},
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);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateInsertionEffect(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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = 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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
warnInvalidHookAccess();
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
updateHookTypesDev();
return updateDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
warnInvalidHookAccess();
updateHookTypesDev();
return updateTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
warnInvalidHookAccess();
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
warnInvalidHookAccess();
updateHookTypesDev();
return updateId();
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return updateActionState(action, initialState, permalink);
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
warnInvalidHookAccess();
updateHookTypesDev();
return updateActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
warnInvalidHookAccess();
updateHookTypesDev();
return updateOptimistic(passthrough, reducer);
},
useMemoCache(size: number): Array<any> {
warnInvalidHookAccess();
return useMemoCache(size);
},
useHostTransitionStatus,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
},
};
if (enableUseEffectEventHook) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
warnInvalidHookAccess();
updateHookTypesDev();
return updateEvent(callback);
};
}
InvalidNestedHooksDispatcherOnRerenderInDEV = {
readContext<T>(context: ReactContext<T>): T {
warnInvalidContextAccess();
return readContext(context);
},
use<T>(usable: Usable<T>): T {
warnInvalidHookAccess();
return use(usable);
},
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
warnInvalidHookAccess();
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return readContext(context);
},
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);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateInsertionEffect(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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return rerenderReducer(reducer, initialArg, init);
} finally {
ReactSharedInternals.H = 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 = ReactSharedInternals.H;
ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return rerenderState(initialState);
} finally {
ReactSharedInternals.H = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
warnInvalidHookAccess();
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
warnInvalidHookAccess();
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
warnInvalidHookAccess();
updateHookTypesDev();
return updateId();
},
useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderActionState(action, initialState, permalink);
},
useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderActionState(action, initialState, permalink);
},
useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderOptimistic(passthrough, reducer);
},
useMemoCache(size: number): Array<any> {
warnInvalidHookAccess();
return useMemoCache(size);
},
useHostTransitionStatus,
useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
},
};
if (enableUseEffectEventHook) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
warnInvalidHookAccess();
updateHookTypesDev();
return updateEvent(callback);
};
}
}