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 如何确定它们所属的组件? 的相关文章

  • Jest 和 React 以及导入 CSS 文件时出现语法错误

    我正在尝试让我的第一个 Jest 测试通过 React 和 Babel 我收到以下错误 SyntaxError Users manueldupont test avid sibelius publishing viewer src comp
  • 卸载 React 时删除事件监听器

    我有更高阶的组件 反应如下 export default function InnerComponent class InfiniteScrolling extends React Component constructor props s
  • 以编程方式填写reactjs表单

    我正在编写一个用户脚本 但无法填写由reactjs制作的表单 我的代码 document querySelector id username value email protected cdn cgi l email protection
  • className 属性是否承担 Reactjs 中 id 属性的角色?

    由于 id 属性在 Reactjs 组件中很少使用 因为 id 属性意味着组件不会被重用 那么是否使用 className 属性来代替 id 呢 如果是这样的话 那么 Reactjs 中相当于 HTML 中的 class 属性的是什么 cl
  • IE 中未定义“代理”

    我通过 React Node 构建了一个 Excel 插件Umi https umijs org 我们已经实施了我们的身份验证系统 身份验证在 Chrome 和 Safari 中有效 我刚刚意识到它在 IE11 中不能很好地工作 F12表明
  • 如何在appendchild方法javascript或reactjs中使用文件夹路径中的svg?

    我是 ReactJS 的新手 我坚持将 svg 元素附加到 div 元素 我想实现什么 我有一个动态创建的 div 元素 如下所示 constructor props super props this element document cr
  • 如何在 React 组件中使用 CDN

    我正在尝试使用基于 D3 构建的库 称为 Greuler 来动态渲染图形 它的 npm 包似乎已损坏 当我改用 Greuler CDN 时 index html 中的测试图终于起作用了 但是 我正在开发一个 React 应用程序 并且我希望
  • React Native 中循环 Json 并显示

    How do I go about looping the result i retrieved from Json render function console log this state list contents
  • jest.mock() 在测试内部不起作用,仅在测试外部起作用

    我有一套简单的测试 在某些情况下我想模拟一个模块 而在某些情况下则不想 然而 jest mock 仅当将其置于测试之外时才有效 任何人都知道为什么会这样以及我做错了什么 这是我想要模拟的函数的实际导入 import hasSupport g
  • 为什么使用 getDerivedStateFromProps 而不是 componentDidUpdate?

    正如读到的这个 React Github 问题 https github com facebook react issues 12310我看到越来越多的 的代价render 相对较小 在 React 16 3 中 我想知道为什么要使用新的
  • Typescript 和 React 使用空类型数组设置初始状态

    假设我有以下片段 interface State alarms Alarm export default class Alarms extends React Component lt State gt state alarms 因为我想设
  • Redux 状态 - 最大推荐大小

    我正在构建一个项目管理应用程序 但我仍然在努力解决一个问题 在 Redux 状态中存储什么以及 按需 加载什么 Redux 状态是否有已知的 最大推荐大小 几十千字节 几百千字节 兆字节单位 请记住 归根结底 Redux 只是 如何 将信息
  • 如何使用 Jest 测试 React 渲染的异步数据?

    我使用 React 进行渲染 使用 Jest Jasmine 进行测试 我用旧的 Jest Jasmine 编写了测试waitsFor and runs但这些现在在 Jasmine 2 中已经消失了 我不知道如何用新的替换done asyn
  • AppBar 在 React 路由之间消失

    我有一个几乎可以工作的简单反应路由项目 我有一个AppBar and a Drawer 使用穆伊 抽屉里有三个项目 它们将重新路由应用程序 我的路由工作正常 但我遇到的问题是AppBar 因此一旦您进入某个页面 应用程序其余部分的导航就不再
  • 如何测试 google.maps.Geocoder?

    你好 我正在尝试编写一个 简单 测试来检查反应类组件状态更改 具体来说 我正在测试如果 Google 成功对我发送的某些字符串 地址 进行地理编码 lat 纬度 和 lng 经度 状态是否会发生变化 这是我想测试的示例 i e the la
  • React 文件预览 (FIREBASE)

    我目前将文件存储在 Firebase 存储中 我希望能够实时生成每个文件的文件预览 映射 例如 PDF 文件会将第一页显示为图像 docx 将是文档的第一页 pptx 将是第一张幻灯片 未知文档将是默认文档符号 有人知道有什么好的服务可以轻
  • React Material UI CardMedia 视频组件未播放

    我看到缩略图 但无法启动或停止视频 还有什么方法可以让它自动播放和重复吗 https material ui com api card media https material ui com api card media
  • 在玩笑中运行普通转换后如何转换模块

    用笑话测试 React 组件 其中一些组件使用 OpenLayers ol 软件包 v5 2 0 在 ol 包 v4 中 我应用了 transformIgnorePatterns 来转换 ol 包 jest transformIgnoreP
  • 如何将背景图像仅应用于一个反应页面而不是整个应用程序?

    注册页面示例 register background image linear gradient to right ff5722 0 ff9800 100 margin top 150px important div div div div
  • 在 webpack 2.x 中使用 autoprefixer 和 postcss

    如何使用autoprefixer使用 webpack 2 x 以前 它曾经是这样的 module loaders test scss loader style css sass postcss postcss gt return autop

随机推荐