React hooks 如何确定它们所属的组件?

2024-05-11

我注意到,当我使用反应钩子时,子组件的状态更改不会重新渲染没有状态更改的父组件。通过此代码沙箱可以看到这一点:https://codesandbox.io/s/kmx6nqr4o https://codesandbox.io/s/kmx6nqr4o

由于缺少将组件作为参数或绑定上下文传递给钩子,我错误地认为反应钩子/状态更改只是触发了整个应用程序重新渲染,就像 mithril 的工作原理以及 React 的作用一样。设计原则 https://reactjs.org/docs/design-principles.html#scheduling states:

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

相反,反应钩子似乎知道它们关联到哪个组件,因此渲染引擎知道只更新该单个组件,并且从不调用render在其他任何事情上,反对上面所说的 React 设计原则文档。

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

  2. 这个关联是如何做到的,以便反应知道只调用render在状态发生改变的组件上,而不是在没有状态改变的组件上? (在代码沙箱中,尽管子元素的状态发生变化,父元素的render从未被调用)

  3. 当您将 useState 和 setState 的用法抽象为自定义钩子函数时,这种关联如何仍然有效? (正如代码沙箱所做的那样setInterval hook)

似乎答案就在这条线索的某个地方解决调度程序 https://github.com/facebook/react/blob/fef40c061ec7db03b6b0faa7608788d625cf78db/packages/react/src/ReactHooks.js#L16-L23, 反应当前所有者 https://github.com/facebook/react/blob/fef40c061ec7db03b6b0faa7608788d625cf78db/packages/react/src/ReactCurrentOwner.js, 反应调节器 https://github.com/facebook/react/tree/fef40c061ec7db03b6b0faa7608788d625cf78db/packages/react-reconciler.


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

  • 写完这个答案后我发现了一篇深入的文章 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/
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React hooks 如何确定它们所属的组件? 的相关文章

随机推荐