首先,如果您正在寻找有关钩子如何工作以及它们如何知道它们绑定到哪个组件实例的概念性解释,请查看以下内容:
这个问题的目的(如果我正确理解了问题的意图)是深入了解实际的实现细节,即 React 如何通过useState
钩子返回的 setter 知道当状态发生变化时要重新渲染哪个组件实例。因为这将深入研究 React 实现细节,所以随着 React 实现的发展,它肯定会逐渐变得不那么准确。在引用 React 代码的部分内容时,我将删除我认为混淆了回答这个问题最相关方面的行。
理解其工作原理的第一步是在 React 中找到相关代码。我将重点关注三个要点:
- 为组件实例执行渲染逻辑的代码(即对于功能组件,执行组件功能的代码)
- 该
useState
代码
- 通过调用返回的 setter 触发的代码
useState
第 1 部分React 如何知道调用 的组件实例useState
?
查找执行渲染逻辑的 React 代码的一种方法是从渲染函数中抛出错误。问题的 CodeSandbox 的以下修改提供了触发该错误的简单方法:
这为我们提供了以下堆栈跟踪:
Uncaught Error: Error in child render
at Child (index.js? [sm]:24)
at renderWithHooks (react-dom.development.js:15108)
at updateFunctionComponent (react-dom.development.js:16925)
at beginWork$1 (react-dom.development.js:18498)
at HTMLUnknownElement.callCallback (react-dom.development.js:347)
at Object.invokeGuardedCallbackDev (react-dom.development.js:397)
at invokeGuardedCallback (react-dom.development.js:454)
at beginWork$$1 (react-dom.development.js:23217)
at performUnitOfWork (react-dom.development.js:22208)
at workLoopSync (react-dom.development.js:22185)
at renderRoot (react-dom.development.js:21878)
at runRootCallback (react-dom.development.js:21554)
at eval (react-dom.development.js:11353)
at unstable_runWithPriority (scheduler.development.js:643)
at runWithPriority$2 (react-dom.development.js:11305)
at flushSyncCallbackQueueImpl (react-dom.development.js:11349)
at flushSyncCallbackQueue (react-dom.development.js:11338)
at discreteUpdates$1 (react-dom.development.js:21677)
at discreteUpdates (react-dom.development.js:2359)
at dispatchDiscreteEvent (react-dom.development.js:5979)
所以首先我将重点放在renderWithHooks
. 这驻留在ReactFiberHooks 中。如果你想探索更多的路径到这一点,堆栈跟踪中的关键点是beginWork和updateFunctionComponent函数,它们都在 ReactFiberBeginWork.js 中。
这是最相关的代码:
currentlyRenderingFiber = workInProgress;
nextCurrentHook = current !== null ? current.memoizedState : null;
ReactCurrentDispatcher.current =
nextCurrentHook === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, refOrContext);
currentlyRenderingFiber = null;
currentlyRenderingFiber
表示正在呈现的组件实例。这就是 React 知道useState
调用与哪个组件实例相关的方式。无论你调用的自定义钩子有多深useState
,它仍然会出现在你的组件渲染中(发生在这一行let children = Component(props, refOrContext);
:),所以 React 仍然会currentlyRenderingFiber
在渲染之前知道它与集合相关联。
设置后currentlyRenderingFiber
,它还设置了当前的调度程序。请注意,对于组件 ( HooksDispatcherOnMount
)的初始安装和组件的重新渲染( ) ,调度程序是不同的HooksDispatcherOnUpdate
。我们将在第 2 部分中回到这个方面。
第 2 部分发生了useState
什么?
在ReactHooks 中,我们可以找到以下内容:
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
这将使我们进入ReactFiberHooks 中的useState
函数。这对于组件的初始安装与更新(即重新渲染)的映射不同。
const HooksDispatcherOnMount: Dispatcher = {
useReducer: mountReducer,
useState: mountState,
};
const HooksDispatcherOnUpdate: Dispatcher = {
useReducer: updateReducer,
useState: updateState,
};
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
// Flow doesn't know this is non-null, but we do.
((currentlyRenderingFiber: any): Fiber),
queue,
): any));
return [hook.memoizedState, dispatch];
}
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
mountState
上面代码中需要注意的重要部分是dispatch
变量。该变量是您的状态的设置器,并mountState
在最后返回:return [hook.memoizedState, dispatch];
。dispatch
只是一个dispatchAction
函数(也在 ReactFiberHooks.js 中),带有一些绑定到它的参数,包括currentlyRenderingFiber
和queue
。我们将在第 3 部分中了解这些是如何发挥作用的,但请注意它们queue.dispatch
指向的是同一个dispatch
函数。
useState
委托updateReducer
(也在ReactFiberHooks 中)更新(重新渲染)的情况。我有意省略了updateReducer
下面的许多细节,只是为了看看它如何处理返回与初始调用相同的 setter。
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
您可以在上面看到queue.dispatch
用于在重新渲染时返回相同的 setter。
第 3 部分当您调用 返回的 setter 时会发生什么useState
?
这是dispatchAction的签名:
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A)
您的新状态值将是action
. 该fiber
工作queue
将自动由于传递bind
呼叫mountState
。将fiber
(同一个对象作为较早保存currentlyRenderingFiber
它代表的组件实例)将在同一组件实例指出,呼吁useState
让react过来的时候你给它一个新的状态值排队特定组件的重新渲染。
一些用于理解 React Fiber Reconciler 以及什么是 Fiber 的额外资源: