首先,如果您正在寻找有关钩子如何工作以及它们如何知道它们绑定到哪个组件实例的概念性解释,请参阅以下内容:
- 写完这个答案后我发现了一篇深入的文章 https://medium.com/the-guild/under-the-hood-of-reacts-hooks-system-eb59638c9dba
- 挂钩常见问题解答 https://reactjs.org/docs/hooks-faq.html#how-does-react-associate-hook-calls-with-components
- 相关的 StackOverflow 问题 https://stackoverflow.com/questions/53729917/react-hooks-whats-happening-under-the-hood
- 丹·阿布拉莫夫 (Dan Abramov) 的相关博客文章 https://overreacted.io/how-does-setstate-know-what-to-do/
这个问题的目的(如果我正确理解了问题的意图)是更深入地了解实际的实现细节,即当状态通过返回的 setter 发生变化时,React 如何知道要重新渲染哪个组件实例。useState
钩。因为这将深入研究 React 实现细节,随着 React 实现随着时间的推移,它肯定会逐渐变得不那么准确。当引用 React 代码的某些部分时,我将删除那些我认为混淆了回答这个问题的最相关方面的行。
了解其工作原理的第一步是在 React 中找到相关代码。我主要讲三个要点:
- 执行组件实例渲染逻辑的代码(即对于功能组件,执行组件功能的代码)
- the
useState
code
- 通过调用返回的 setter 触发的代码
useState
Part 1React 如何知道调用的组件实例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 https://github.com/facebook/react/blob/v16.9.0/packages/react-reconciler/src/ReactFiberHooks.js#L365。如果您想探索到达这一点的更多路径,堆栈跟踪中较高的关键点是开始工作 https://github.com/facebook/react/blob/v16.9.0/packages/react-reconciler/src/ReactFiberBeginWork.js#L2632 and 更新函数组件 https://github.com/facebook/react/blob/v16.9.0/packages/react-reconciler/src/ReactFiberBeginWork.js#L595函数都在 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 部分中回到这个方面。
Part 2发生了什么useState
?
In 反应钩子 https://github.com/facebook/react/blob/v16.9.0/packages/react/src/ReactHooks.js#L77我们可以找到以下内容:
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
这将使我们到达useState
函数于ReactFiberHooks https://github.com/facebook/react/blob/v16.9.0/packages/react-reconciler/src/ReactFiberHooks.js#L1286。组件的初始安装与更新(即重新渲染)的映射方式不同。
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
and queue
。我们将在第 3 部分中了解它们如何发挥作用,但请注意queue.dispatch
指向同一点dispatch
功能。
useState
代表updateReducer
(也在ReactFiberHooks https://github.com/facebook/react/blob/v16.9.0/packages/react-reconciler/src/ReactFiberHooks.js#L658)用于更新(重新渲染)情况。我故意省略了许多细节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
用于在重新渲染时返回相同的设置器。
Part 3当您调用返回的 setter 时会发生什么useState
?
这是签名调度动作 https://github.com/facebook/react/blob/v16.9.0/packages/react-reconciler/src/ReactFiberHooks.js#L658:
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A)
您的新状态值将是action
. The fiber
和工作queue
将会自动通过,因为bind
打电话进来mountState
. The fiber
(之前保存的同一对象currentlyRenderingFiber
代表组件实例)将指向调用的同一个组件实例useState
当你给它一个新的状态值时,允许 React 将该特定组件的重新渲染排队。
用于了解 React Fiber Reconciler 以及纤维是什么的一些额外资源:
- 光纤调节器部分https://reactjs.org/docs/codebase-overview.html https://reactjs.org/docs/codebase-overview.html
- https://github.com/acdlite/react-fibre-architecture https://github.com/acdlite/react-fiber-architecture
- https://blog.ag-grid.com/index.php/2018/11/29/inside-optical-in-deep-overview-of-the-new-reconciliation-algorithm-in-react/ https://blog.ag-grid.com/index.php/2018/11/29/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react/