我注意到当我使用react挂钩时,子组件的状态更改不会重新呈现没有状态更改的父组件。此代码沙箱可以看到这一点:https://codesandbox.io/s/kmx6nqr4o
由于缺少将组件作为参数或作为绑定上下文传递给钩子的原因,我错误地认为反应钩子/状态更改只是触发了整个应用程序的重新渲染,例如秘银的工作方式以及React的{ {3}}指出:
React递归地遍历树,并在单个刻度内调用整个更新树的渲染函数。
相反,似乎React钩子知道它们与哪个组件相关联,因此,渲染引擎知道仅更新单个组件,而从不调用render
,这与React的设计原则文档相反如上所述。
挂钩和组件之间的关联如何完成?
该关联如何实现,以便react知道仅在状态发生变化的组件上调用render
,而没有状态发生变化的组件则调用? (在代码沙箱中,尽管子状态更改,但从未调用父元素的render
)
当您将useState和setState的用法抽象到自定义钩子函数中时,此关联仍如何起作用? (如代码沙箱使用setInterval
钩子所做的一样)
似乎答案位于Design Principles,resolveDispatcher,ReactCurrentOwner这条线索中。
答案 0 :(得分:13)
首先,如果您要寻找有关钩子如何工作以及它们如何知道它们绑定到的组件实例的概念性解释,请参见以下内容:
这个问题的目的(如果我正确理解了这个问题的意图)是为了更深入地了解实际的实现细节,即当状态发生变化时,React如何通过{{ 1}}钩子。因为这将深入研究React实现细节,所以随着React实现的发展,它肯定会逐渐变得不那么准确。在引用React代码的某些部分时,我将删除那些混淆了最相关方面的行以回答该问题。
了解其工作原理的第一步是在React中找到相关的代码。我将重点介绍三个要点:
useState
代码useState
返回的setter触发的代码 第1部分:React如何知道名为useState
的组件实例?
找到执行渲染逻辑的React代码的一种方法是从渲染函数抛出错误。问题的CodeSandbox的以下修改提供了一种触发该错误的简便方法:
这为我们提供了以下堆栈跟踪:
useState
因此,我首先关注at Child (index.js? [sm]:24)
at renderWithHooks (react-dom.development.js:12749)
at updateFunctionComponent (react-dom.development.js:14286)
at beginWork (react-dom.development.js:15215)
at performUnitOfWork (react-dom.development.js:18789)
at workLoop (react-dom.development.js:18829)
at renderRoot (react-dom.development.js:18912)
at performWorkOnRoot (react-dom.development.js:19809)
at performWork (react-dom.development.js:19721)
at performSyncWork (react-dom.development.js:19695)
at interactiveUpdates$1 (react-dom.development.js:19964)
at interactiveUpdates (react-dom.development.js:2169)
at dispatchInteractiveEvent (react-dom.development.js:4878)
。它位于ReactFiberHooks中。
以下是最相关的代码:
renderWithHooks
currentlyRenderingFiber = workInProgress;
firstCurrentHook = nextCurrentHook = current !== null ? current.memoizedState : null;
ReactCurrentDispatcher.current = nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate;
let children = Component(props, refOrContext);
currentlyRenderingFiber = null;
代表正在渲染的组件实例。这就是React知道currentlyRenderingFiber
调用与哪个组件实例相关的方式。无论您调用useState
的自定义钩子有多深,它仍会在组件的呈现中发生(发生在此行:useState
),因此React仍会知道它与{{1 }}在渲染之前设置。
设置let children = Component(props, refOrContext);
后,它还会设置当前调度程序。请注意,对于组件的初始安装(currentlyRenderingFiber
)和重新呈现组件(currentlyRenderingFiber
)而言,调度程序是不同的。我们将在第2部分中回到这一方面。
第2部分,HooksDispatcherOnMount
中会发生什么?
在ReactHooks中,我们可以找到以下内容:
HooksDispatcherOnUpdate
这将使我们进入ReactFiberHooks中的useState
函数。对于组件的初始安装与更新(即重新渲染),此映射的映射方式不同。
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
上面的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,
eagerReducer: basicStateReducer,
eagerState: (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];
函数(也在ReactFiberHooks.js中),绑定了一些参数,包括dispatch
和dispatchAction
。我们将在第3部分中研究它们如何发挥作用,但是请注意currentlyRenderingFiber
指向相同的queue
函数。
queue.dispatch
委托dispatch
(也在ReactFiberHooks中)进行更新(重新渲染)。除了查看useState
会如何处理返回与初始调用相同的setter之外,我特意省略了下面的updateReducer
的许多细节。
updateReducer
您可以在上方看到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];
}
用于在重新渲染时返回相同的setter。
第3部分:当您呼叫queue.dispatch
返回的设置器时会发生什么?
以下是useState
的签名:
dispatchAction
您的新状态值为function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A)
。 action
中的fiber
调用会自动传递queue
和作品bind
。 mountState
(与先前保存的fiber
相同的对象代表了组件实例)将指向名为currentlyRenderingFiber
的相同组件实例,从而使React可以将该特定对象的重新渲染排队组件,当您为其赋予新的状态值时。
一些其他资源,用于了解React Fiber Reconciler和什么是光纤: