你可能一直在使用错误的 useEffect
反应开发
你可能一直在使用错误的 useEffect
我最近与 eslint 详尽的 deps 规则幽会。这让我陷入了困境,并意识到我一直在错误地使用 useEffect 钩子。在本文中,我将讨论我对 useEffect 钩子的错误理解以及正确看待它的方式。
在我开始之前,一些背景。当 React 中的功能组件只是无状态组件并且我们使用基于类的组件编写大部分逻辑时,我开始了 React 开发。当 React 引入允许功能组件有状态的钩子并鼓励 React 开发人员使用无类时,我像大多数开发人员一样迅速做出了转变。
误解useEffect的原因
由于我是从基于类的组件迁移到函数式组件,所以我犯了一个错误,即通过基于类的组件的棱镜来看待函数式组件。这沉淀了我对 useEffect 钩子的错误理解。事后看来,我可以看到造成这种误解的两个主要原因。
在基于类的组件中,我们使用了 componentWillMount、componentDidMount 和 componentDidUpdate 等生命周期方法。在功能组件中,我们没有这些生命周期方法,而是使用了 useEffect 钩子。我对 useEffect 钩子的理解有缺陷的两个主要原因之一是我认为它是这些生命周期方法的直接替代品。
在说第二个主要原因之前,我们先简单看一下 useEffect 钩子。这个 useEffect 钩子接受两个参数。第一个参数是 React 在渲染后调用的回调函数。第二个参数是一个指定钩子依赖关系的数组。
第二个主要原因是我将这个依赖数组视为有条件地触发 useEffect 挂钩的一种方式。
现在,让我详细说明这两个原因。
将 useEffect 视为生命周期方法的替代品
在基于类的组件中,我们使用生命周期方法来执行网络调用等功能。因此,很容易将 useEffect 视为功能组件中所有生命周期方法的一站式商店。但是 useEffect 背后的想法与生命周期方法根本不同。
正确看待 useEffect 钩子的方法
在功能组件中, 我们使用 useEffect 来同步对 React 树之外的状态和道具的更改 .让我们看看这意味着什么。
当组件的状态发生变化时,React 会尝试通过重新渲染组件来将此更改与 DOM 同步。类似地,useEffect hook 用于同步 React 树之外的 state 和 prop 更改。
例如,让我们以带有分页的表格为例。每次活动页面发生变化时,我们都必须发送一个 API 请求来检索下一组行。我们可以将活动页面设置为一个状态。并在活动页面状态更改时发送 API 请求。我们可以通过从 useEffect 挂钩中发送 API 请求来做到这一点。在这里,useEffect 钩子通过从后端获取正确的行集来同步 React 树之外的状态更改。
因此,我们不会担心这个 API 请求是在组件挂载之前还是之后发送,还是在更新时发送。每次状态更改时都会发送此 API 请求。换句话说,useEffect 将状态更改与来自后端的数据同步。这是看待 useEffect 的正确方法。
现在,很自然地想知道我们如何仅在组件挂载时才调度 API 请求。好吧,我们可以为此使用依赖数组。但是很容易把这个数组想象成我们用来触发 useEffect 钩子的东西。这是一个不正确的心智模型。这将我们带到第二个主要原因。
将依赖数组视为有条件地触发 useEffect 钩子的一种方式
让我们再次考虑分页示例。每当基于类的组件中的活动页面发生变化时,我们将如何发送 API 请求?我们可能会使用 componentDidUpdate 生命周期方法并将之前的活动页面状态与当前状态进行比较。然后,如果它们不同,我们将发送一个 API 请求。
有了这种心态,人们可以将依赖数组视为一种在活动页面状态发生变化时要求 React 触发 useEffect 钩子的方式。虽然这并不完全不正确,但这种心智模型可能会产生不良后果。
这个心智模型非常适合我们的例子。我们可以在钩子中发出 API 请求,并将活动页面状态作为依赖项传递,因此每次活动页面状态更改时都会触发钩子。然而,让我们对我们的例子做一个小的改动,看看这个心智模型是否成立。
这种有缺陷的心智模型可能有效,但可能会导致错误
假设我们还有一个文本框,允许您在移动到下一页时指定要获取的行数。我们可以将文本框的值绑定到一个状态。因此,我们必须在 useEffect 挂钩中使用此状态,因为我们应该指定 API 请求中的行数。但是由于我们只需要在活动页面状态发生变化时“触发”钩子,因此我们只在依赖数组中指定活动页面状态。
这应该像我们预期的那样工作。但是,exhaustive-deps eslint 规则现在应该发出警告。这是因为我们没有将行数状态传递给依赖数组。这对我们来说可能没有意义,因为我们使用当前代码获得了预期的输出。但是我们在这里所做的本质上是滥用 useEffect 钩子来获得我们想要的东西。所以,我们所做的只是一个 hack,而不是一个适当的解决方案。虽然这可能在这里奏效,但这种思维模型和方法保证会产生错误代码。
查看依赖数组的正确方法
那么,关于依赖数组的正确心智模型应该是什么?好吧,实际上依赖数组并没有提供任何功能上的好处。它是一个优化功能。正如我们之前看到的,useEffect 挂钩用于同步对 React 树之外的状态和道具的更改。同步发生在每次渲染期间。换句话说,React 在每次渲染之后调用 useEffect 钩子。
但是,这可能不是必需的。如果我们以我们的示例为例,在每次渲染之后发送 API 请求将是一种浪费。依赖数组用于解决这个问题。依赖数组让 React 了解 useEffect 钩子中使用了哪些状态和道具,因此 React 仅在这些状态和道具发生变化时才需要运行此钩子。
因此,我们需要将函数组件范围内的所有内容,包括状态、道具、函数、变量和 useRefs,我们在 useEffect 中使用的所有内容都传递到依赖数组中。这告诉 React 我们在 useEffect 钩子中使用了什么,以便 React 可以调用 useEffect 钩子以仅在这些依赖项发生更改时同步更改。
回到我们的示例,我们必须将活动页面状态和行数状态传递给依赖数组,因为这些是我们在 useEffect 挂钩中使用的唯一内容。所以,React 只有在这两种状态发生变化时才会调用这个钩子进行同步。这应该可以修复详尽的部门警告。
两个错误不能构成一个正确
但是,这将破坏组件的功能,因为 API 请求现在将在活动页面状态和行数状态更改时被分派。但我们只想在活动页面状态发生变化时发送 API 请求。这肯定会给大多数开发者带来生存危机。这最终迫使一些开发人员考虑禁用有问题的 eslint 规则。
然而,我们必须明白,这个难题是在考虑基于类的组件时编写函数组件的直接结果。如果我们对 useEffect 钩子采用正确的思维模型,那么我们应该能够以正确的方式适应不同的需求。让我们看看如何使用正确的心智模型来实现这一点。
现在让我们总结一下 useEffect 挂钩的正确心智模型。
正确的心理模型
useEffect 挂钩用于同步对功能组件范围内声明的任何内容的更改,例如 React 树之外的状态、道具、函数、变量和 useRefs。我们使用依赖数组告诉 React 我们在 useEffect 钩子中使用了这些依赖项,这样 React 只有在这些依赖项发生变化时才需要调用这个钩子。这是一种优化技术。
有了这个心智模型,让我们看看我们如何解决大多数常见的用例 另一篇文章 .
最初发表于 https://www.thearmchaircritic.org 2022 年 8 月 28 日。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明