由于缺少将组件作为参数或绑定上下文传递给钩子的原因,我错误地认为反应钩子/状态更改只会触发整个应用程序的重新渲染,例如秘银的工作方式以及React的设计原则指出的内容:
React递归地遍历树,并在一个滴答中调用整个更新树的渲染函数。
相反,似乎renderReact 钩子知道它们与哪个组件相关联,因此,渲染引擎知道仅更新单个组件,而从不调用其他任何东西,这与React的《设计原则》文档相反。
render
挂钩和组件之间的关联如何完成?
这种关联如何使之反应知道只调用render状态已更改的组件,而不调用那些状态未更改的组件?(在代码沙箱中,尽管子状态更改,但render永远不会调用父元素)
当您将useState和setState的用法抽象到自定义钩子函数中时,此关联仍如何工作?(就像代码沙箱对setInterval钩子所做的那样)
setInterval
似乎答案在此线索的某个位置resolveDispatcher,ReactCurrentOwner,react-reconciler。
首先,如果您正在寻找有关钩子如何工作以及它们如何知道它们绑定到的组件实例的概念性解释,请参见以下内容:
这个问题的目的(如果我正确理解了这个问题的意图)是为了更深入地了解实际实现细节,即当状态通过useState钩子返回的设置程序更改时,React如何知道要重新渲染哪个组件实例。因为这将深入研究React实现细节,所以随着React实现的发展,肯定会逐渐变得不那么准确。当引用部分React代码时,我将删除那些我觉得最模糊的方面来回答这个问题。
useState
理解这是如何工作的第一步是在React中找到相关的代码。我将重点介绍三个要点:
第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中。
renderWithHooks
这是最相关的代码:
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
let children = Component(props, refOrContext);
设置之后currentlyRenderingFiber,它还会设置当前调度程序。请注意,对于初始安装组件(HooksDispatcherOnMount)和重新呈现组件(HooksDispatcherOnUpdate),调度程序有所不同。我们将在第2部分中回到这一方面。
HooksDispatcherOnMount
HooksDispatcherOnUpdate
第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功能。
mountState
dispatch
return [hook.memoizedState, dispatch];
dispatchAction
queue
queue.dispatch
useState委托给updateReducer(也在ReactFiberHooks中)进行更新(重新渲染)。我故意省略了updateReducer下面的许多详细信息,只是要看它如何处理返回与初始调用相同的setter。
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]; }
您可以在上面看到,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让反应过来的时候你给它一个新的状态值排队特定组件的重新渲染。
action
fiber
bind
一些其他资源,用于了解反应光纤调节器以及什么是光纤: