Reactjs react钩子如何确定它们用于的组件?

Reactjs react钩子如何确定它们用于的组件?,reactjs,react-hooks,Reactjs,React Hooks,我注意到,当我使用react钩子时,子组件的状态更改不会重新引发没有状态更改的父组件。这可以通过以下代码沙盒看到: 由于没有将组件作为参数或绑定上下文传递给钩子,我错误地认为react钩子/状态更改只会触发整个应用程序重新加载程序,比如mithril的工作方式以及react的状态: React递归地遍历树,并在一次勾选期间调用整个更新树的渲染函数 相反,react钩子似乎知道它们与哪个组件关联,因此,渲染引擎只知道更新单个组件,而不调用任何其他组件的render,这与react的设计原则文档上面

我注意到,当我使用react钩子时,子组件的状态更改不会重新引发没有状态更改的父组件。这可以通过以下代码沙盒看到:

由于没有将组件作为参数或绑定上下文传递给钩子,我错误地认为react钩子/状态更改只会触发整个应用程序重新加载程序,比如mithril的工作方式以及react的状态:

React递归地遍历树,并在一次勾选期间调用整个更新树的渲染函数

相反,react钩子似乎知道它们与哪个组件关联,因此,渲染引擎只知道更新单个组件,而不调用任何其他组件的
render
,这与react的设计原则文档上面所说的相反

  • 钩子和组件之间的关联是如何完成的

  • 这种关联是如何使react只对状态已更改的组件调用
    render
    ,而不调用未更改状态的组件的?(在代码沙盒中,尽管子元素的状态发生了更改,但从未调用父元素的
    渲染

  • 当您将useState和setState的用法抽象为自定义钩子函数时,这种关联是如何工作的?(正如代码沙盒使用
    setInterval
    hook所做的那样)


  • 答案似乎就在这条线索的某个地方。

    首先,如果您想从概念上解释钩子是如何工作的,以及它们是如何知道它们绑定到哪个组件实例的,请参见以下内容:

        export function useState<S>(initialState: (() => S) | S) {
          const dispatcher = resolveDispatcher();
          return dispatcher.useState(initialState);
        }
    
    这个问题的目的(如果我正确理解了问题的意图)是深入了解实际的实现细节,即React如何知道当状态通过
    useState
    hook返回的setter更改时,要重新呈现哪个组件实例。因为这将深入研究React实现的细节,所以随着React实现的发展,其准确性肯定会逐渐降低。在引用React代码的部分内容时,我将删除那些我觉得在回答这个问题时会混淆最相关方面的行

    理解其工作原理的第一步是在React中找到相关代码。我将集中讨论三个要点:

    • 为组件实例执行呈现逻辑的代码(即,对于功能组件,执行组件功能的代码)
    • useState
      代码
    • 调用由
      useState
    第1部分React如何知道调用
    useState的组件实例

    查找执行渲染逻辑的React代码的一种方法是从render函数中抛出错误。以下对问题的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)
    
    因此,首先我将重点介绍
    带挂钩的渲染器。这存在于你的内心。如果您想探索到这一点的更多路径,堆栈跟踪中较高的关键点是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如何知道调用与哪个组件实例相关。无论您调用的自定义钩子有多深,它仍然会出现在组件的渲染中(发生在这一行:
    let children=component(props,refOrContext);
    ),因此React仍会知道它与渲染之前设置的
    currentlyRenderingFiber
    有关

    设置
    currentlyRenderingFiber
    后,它还会设置当前调度程序。请注意,对于组件的初始装载(
    HooksDispatcherOnMount
    )和组件的重新呈现(
    HooksDispatcherOnUpdate
    ),调度程序是不同的。我们将在第2部分回到这方面

    第2部分
    useState
    中发生了什么

    在中,我们可以找到以下内容:

        export function useState<S>(initialState: (() => S) | S) {
          const dispatcher = resolveDispatcher();
          return dispatcher.useState(initialState);
        }
    
    上述
    mountState
    代码中需要注意的重要部分是
    dispatch
    变量。该变量是您状态的设置器,并从末尾的
    mountState
    返回:
    return[hook.memorizedstate,dispatch]
    dispatch
    只是
    dispatchAction
    函数(也在ReactFiberHooks.js中),它绑定了一些参数,包括
    currentlyRenderingFiber
    queue
    。我们将在第3部分中了解这些函数是如何发挥作用的,但请注意,
    queue.dispatch
    指向相同的
    dispatch
    函数

    useState
    将更新(重新渲染)案例委托给
    updateReducer
    (也在中)。我有意省略下面的
    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];
        }
    
    您的新状态值将是
    操作
    。由于
    bind
    调用
    mountState
    ,光纤
    和工作
    队列将自动传递。
    fiber
    (先前保存为表示组件实例的
    currentlyRenderingFiber
    的同一对象)将指向名为
    useState
    的同一组件实例,允许React在您为该特定组件提供新状态值时排队重新呈现该组件

    了解React光纤调节器以及光纤是什么的一些额外资源:

    • 纤维调节器部分

    @Li357如果这是唯一的事情,那么父母的
    function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A)