当前位置: 首页 > 面试题库 >

反应挂钩如何确定它们所要使用的组件?

董同
2023-03-14
问题内容
我注意到当我使用react挂钩时,子组件的状态更改不会重新呈现没有状态更改的父组件。可以通过以下代码沙箱查看: https
//codesandbox.io/s/kmx6nqr4o

由于缺少将组件作为参数或绑定上下文传递给钩子的原因,我错误地认为反应钩子/状态更改只会触发整个应用程序的重新渲染,例如秘银的工作方式以及React的设计原则指出的内容:

React递归地遍历树,并在一个滴答中调用整个更新树的渲染函数。

相反,似乎renderReact
钩子知道它们与哪个组件相关联,因此,渲染引擎知道仅更新单个组件,而从不调用其他任何东西,这与React的《设计原则》文档相反。

  1. 挂钩和组件之间的关联如何完成?

  2. 这种关联如何使之反应知道只调用render状态已更改的组件,而不调用那些状态未更改的组件?(在代码沙箱中,尽管子状态更改,但render永远不会调用父元素)

  3. 当您将useState和setState的用法抽象到自定义钩子函数中时,此关联仍如何工作?(就像代码沙箱对setInterval钩子所做的那样)

似乎答案在此线索的某个位置resolveDispatcher,ReactCurrentOwner,react-reconciler。


问题答案:

首先,如果您正在寻找有关钩子如何工作以及它们如何知道它们绑定到的组件实例的概念性解释,请参见以下内容:

  • 我写这个答案后发现的深入文章
  • 挂钩常见问题
  • Dan Abramov的相关博客文章

这个问题的目的(如果我正确理解了这个问题的意图)是为了更深入地了解实际实现细节,即当状态通过useState钩子返回的设置程序更改时,React如何知道要重新渲染哪个组件实例。因为这将深入研究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中),绑定了一些参数的函数包括currentlyRenderingFiberqueue。我们将在第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让反应过来的时候你给它一个新的状态值排队特定组件的重新渲染。

一些其他资源,用于了解反应光纤调节器以及什么是光纤:

  • https://reactjs.org/docs/codebase-overview.html的光纤协调器部分
  • https://github.com/acdlite/react-fiber-architecture
  • https://blog.ag-grid.com/index.php/2018/11/29/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react/


 类似资料:
  • 我注意到,当我使用react钩子时,子组件的状态变化不会重新呈现没有状态变化的父组件。下面的代码沙箱可以看到这一点:https://codesandbox.io/s/kmx6nqr4o 由于没有将组件作为参数或绑定上下文传递给钩子,我错误地认为react钩子/状态更改只是触发了整个应用程序重新程序,就像mithril是如何工作的,以及react的设计原则所述: React递归地遍历树,并在一次滴答

  • 我正在构建一个应用程序(使用TypeScript),我面临一个问题,该解决方案需要识别不同的子组件的可能性。基本上是这样的: 我已经搜索了一段时间,许多,如果不是所有接受的答案(像这里:只允许反应组件中特定类型的子级),都是使用()。不幸的是,也有人说在生产中可能会缩小,所以我不能依赖它。 我发现的唯一方法是“强制”一个自己的显示名称。基本上,我创建了一个接口,然后为每个应该可以识别的组件扩展它。

  • 我试图理解新的React钩子及其用例。 我的目标是一个单一的组成部分,计算起来,也每x滴答计数另一个计数器。 我使用useEffect和useState实现了它,主要有两个问题: 1。组件在调用超时之前卸载时发生内存泄漏(使用react router导航时) 2。该组件在每个刻度上渲染两次,因为useEffect和useState都会触发渲染。 我认为解决方案是使用useRef或usemo,但我还

  • Java Docs说Java Docs 公共类BufferedReader扩展阅读器 但是通过挂钩Filereader,我们使用Filereader的读取方法来读取一次读取一个字符的文件,所以如果我的文件包含2000个字符,Filereader首先会一次读取2000个字符,并将其转移到缓冲区,我们将使用bufferedreader从缓冲区读取,那么它如何提高性能?我们可以只用Filereader来

  • 问题内容: 在带有钩子的React中,更新状态的正确方法是嵌套对象是什么? 一个人怎么会使用到的更新来(附加一个字段)? (改变价值)? 问题答案: 您可以像这样传递新值